diff --git a/app/admin/view/auth_rule/edit.html b/app/admin/view/auth_rule/edit.html
index ab1fc5e..841daa1 100644
--- a/app/admin/view/auth_rule/edit.html
+++ b/app/admin/view/auth_rule/edit.html
@@ -39,9 +39,9 @@
diff --git a/app/admin/view/auth_rule/index.html b/app/admin/view/auth_rule/index.html
index 8a0f078..c4267f7 100644
--- a/app/admin/view/auth_rule/index.html
+++ b/app/admin/view/auth_rule/index.html
@@ -51,8 +51,8 @@
defaultToolbar: ['filter', 'print', 'exports'],
cols: [
[
- {type: 'numbers'},
{type: 'checkbox'},
+ {field: 'id', title: 'ID',width: 40},
{field: 'title', title: '菜单名称', minWidth: 165},
{field: 'url', title: '菜单地址', rowspan: 2},
{
@@ -60,10 +60,10 @@
templet: '
'
},
{field: 'authority', title: '权限标识'},
- {field: 'isMenu', title: '类型', templet: type, align: 'center', width: 60},
- //{title: '类型', templet: '
{{d.isMenu ? "菜单" : "按钮"}}
', align: 'center', width: 60},
+ {field: 'ismenu', title: '类型', templet: type, align: 'center', width: 60},
+ // {title: '类型', templet: '
{{d.ismenu ? "菜单" : "按钮"}}
', align: 'center', width: 60},
{field: 'sort', title: '排序', align: 'center', width: 60},
- {field: 'ctime',title: '创建时间'},
+ {field: 'create_time',title: '创建时间'},
{align: 'center', toolbar: '#tbBar', title: '操作', width: 120}
]
],
@@ -72,7 +72,7 @@
//自定义“状态”列
function type(data) {
- var isMenu = data.isMenu;
+ var isMenu = data.ismenu;
var btns = "";
if (isMenu == -1) {
return "目录";
@@ -149,7 +149,7 @@
$.ajax({
type:"post",
url:"{:url('AuthRule/edit')}",
- data:{"id":field.id,"pid":field.pid,"title":field.title,"name":field.name,"icon":field.icon,"sort":field.sort,"ishidden":field.ishidden},
+ data: field,
daType:"json",
success:function (res){
if (res.code == 0) {
@@ -207,7 +207,7 @@
$.ajax({
type:"post",
url:"{:url('AuthRule/add')}",
- data:{"pid":field.pid,"title":field.title,"name":field.name,"icon":field.icon,"sort":field.sort,"ishidden":field.ishidden},
+ data: field,
daType:"json",
success:function (data){
if (data.code == 0) {
diff --git a/app/admin/view/forum/add.html b/app/admin/view/forum/add.html
index 42d3d79..d5329b4 100644
--- a/app/admin/view/forum/add.html
+++ b/app/admin/view/forum/add.html
@@ -109,6 +109,9 @@ layui.config({
upload = layui.upload;
var editor = layui.editor;
+ //获取百度标签标志,tag或者word;
+ var flag = 'word';
+
//如果你是采用模版自带的编辑器,你需要开启以下语句来解析。
var taonystatus = "{:hook('taonystatus')}";
// 编辑器插件启用状态
@@ -160,15 +163,15 @@ layui.config({
if (conf !== "1") {
$("#L_title").on("blur", function () {
var title = $(this).val();
- var flag = "on";
+ var content = $("#L_content").val();
$.ajax({
type: "post",
url: "{:url('Forum/getKeywords')}",
- data: { keywords: keywords, flag: flag },
+ data: { keywords: title, content:content, flag: flag },
daType: "json",
success: function (data) {
if (data.code == 0) {
- $("input[name='tags']").val(data.data);
+ $("input[name='keywords']").val(data.data.join(','));
}
},
});
diff --git a/app/admin/view/forum/tags.html b/app/admin/view/forum/tags.html
index 82059e1..9c4d127 100644
--- a/app/admin/view/forum/tags.html
+++ b/app/admin/view/forum/tags.html
@@ -95,7 +95,7 @@ overflow: visible;
[
{type: 'numbers'},
{type: 'checkbox'}
- ,{field: 'tags', title: '分类名', minWidth: 200}
+ ,{field: 'catename', title: '分类名', minWidth: 200}
,{field: 'ename', title: 'EN别名', width: 100}
,{field: 'detpl',title: '模板', align: 'center',width: 100,templet: '#inputSel'}
,{title: '图标', align: 'center',width: 50,templet: '
'}
@@ -136,25 +136,25 @@ overflow: visible;
type: 2
,title: '编辑分类'
,content: forumTagsForm + '?id='+ data.id
- ,area: ['400px', '450px']
+ ,area: ['400px', '500px']
,btn: ['确定', '取消']
,yes: function(index, layero){
//获取iframe元素的值
var othis = layero.find('iframe').contents().find("#layuiadmin-app-form-tags")
,pid = othis.find('input[name="pid"]').val()
,sort = othis.find('input[name="sort"]').val()
- ,tags = othis.find('input[name="tags"]').val()
+ ,catename = othis.find('input[name="catename"]').val()
,ename = othis.find('input[name="ename"]').val()
,detpl = othis.find('select[name="detpl"]').val()
,icon = othis.find('input[name="icon"]').val()
,desc = othis.find('input[name="desc"]').val();
- if(!tags.replace(/\s/g, '')) return;
+ if(!catename.replace(/\s/g, '')) return;
$.ajax({
type:"post",
url:forumTagsForm,
- data:{"id":data.id,"pid":pid,"sort":sort,"catename":tags,"ename":ename,"detpl":detpl,"icon":icon,"desc":desc},
+ data:{"id":data.id,"pid":pid,"sort":sort,"catename":catename,"ename":ename,"detpl":detpl,"icon":icon,"desc":desc},
daType:"json",
success:function (data){
if(data.code == 0){layer.msg(data.msg,{icon:6,time:2000},function(){
@@ -180,7 +180,7 @@ overflow: visible;
var othis = layero.find('iframe').contents().find("#layuiadmin-app-form-tags").click();
othis.find('input[name="pid"]').val(data.pid)
,othis.find('input[name="sort"]').val(data.sort)
- ,othis.find('input[name="tags"]').val(data.tags)
+ ,othis.find('input[name="catename"]').val(data.catename)
,othis.find('input[name="ename"]').val(data.ename)
,othis.find('input[name="icon"]').val(data.icon)
,othis.find('input[name="desc"]').val(data.desc);
@@ -201,20 +201,20 @@ overflow: visible;
,btn: ['确定', '取消']
,yes: function(index, layero){
var othis = layero.find('iframe').contents().find("#layuiadmin-app-form-tags")
- ,pid = othis.find('input[name="pid"]').val()
- ,sort = othis.find('input[name="sort"]').val()
- ,tags = othis.find('input[name="tags"]').val()
- ,ename = othis.find('input[name="ename"]').val()
- ,detpl = othis.find('select[name="detpl"]').val()
- ,icon = othis.find('input[name="icon"]').val()
- ,desc = othis.find('input[name="desc"]').val();
+ ,pid = othis.find('input[name="pid"]').val()
+ ,sort = othis.find('input[name="sort"]').val()
+ ,catename = othis.find('input[name="catename"]').val()
+ ,ename = othis.find('input[name="ename"]').val()
+ ,detpl = othis.find('select[name="detpl"]').val()
+ ,icon = othis.find('input[name="icon"]').val()
+ ,desc = othis.find('input[name="desc"]').val();
- if(!tags.replace(/\s/g, '')) return;
+ if(!catename.replace(/\s/g, '')) return;
$.ajax({
type:"post",
url:"{:url('Forum/tagsform')}",
- data:{pid:pid,"sort":sort,"catename":tags,"ename":ename,"detpl":detpl,"icon":icon,"desc":desc},
+ data:{pid:pid,"sort":sort,"catename":catename,"ename":ename,"detpl":detpl,"icon":icon,"desc":desc},
daType:"json",
success:function (data){
if (data.code == 0) {
diff --git a/app/admin/view/forum/tagsform.html b/app/admin/view/forum/tagsform.html
index ce13690..f8273cd 100644
--- a/app/admin/view/forum/tagsform.html
+++ b/app/admin/view/forum/tagsform.html
@@ -12,7 +12,7 @@
分类名
-
+
EN别名
diff --git a/app/admin/view/slider/index.html b/app/admin/view/slider/index.html
index 994c9c5..e1578b0 100644
--- a/app/admin/view/slider/index.html
+++ b/app/admin/view/slider/index.html
@@ -74,7 +74,7 @@
,{field: 'slid_href', title: 'URL', minWidth: 250}
,{field: 'slid_color', title: '颜色', width: 80}
,{field: 'slid_start', title: '开始', width: 150, sort: true}
- ,{field: 'slid_over', title: '结束', width: 150, sort: true}
+ ,{field: 'slid_over', title: '结束','escape':false, width: 150, sort: true}
,{field: 'slid_status', title: '状态', width: 80}
,{fixed: 'right', title:'操作', toolbar: '#barDemo', width:150}
]]
diff --git a/app/common.php b/app/common.php
index 05f14e8..292a2e5 100644
--- a/app/common.php
+++ b/app/common.php
@@ -7,6 +7,8 @@ use think\facade\Db;
use think\facade\Session;
use taoser\think\Auth;
+define('DS', DIRECTORY_SEPARATOR);
+
// 应用公共文件
function mailto($to,$title,$content)
{
@@ -210,21 +212,24 @@ function array_child_append($parent, $pid, $child, $child_key_name)
return $parent;
}
-
-//菜单递归
-function getTree($data)
+//菜单无限极分类
+function getTree($data, $pId='0')
{
- $tree = [];
- foreach ($data as $array) {
-
- if(isset($data[$array['pid']])) {
- $data[$array['pid']]['children'][] = &$data[$array['id']];
- //$tree = $data;
- } else {
- $tree[] = &$data[$array['id']];
- }
- }
- return $tree;
+ // 递归
+ $tree = [];
+ foreach ($data as $k => $v) {
+ if ($v['pid'] == $pId) {
+ $child = getTree($data, $v['id']);
+ if(!empty($child)) {
+ $v['children'] = $child;
+ }
+ $tree[] = $v;
+ }
+ }
+ // 排序
+ $cmf_arr = array_column($tree, 'sort');
+ array_multisort($cmf_arr, SORT_ASC, $tree);
+ return $tree;
}
//按钮权限检查
@@ -299,4 +304,5 @@ function find_spider(){
}
}
return false;
-}
\ No newline at end of file
+}
+
diff --git a/app/common/controller/AdminController.php b/app/common/controller/AdminController.php
index 85fd856..7f3ecc1 100644
--- a/app/common/controller/AdminController.php
+++ b/app/common/controller/AdminController.php
@@ -45,8 +45,7 @@ class AdminController extends \app\BaseController
$admin_id = $this->aid;
$auth = new Auth();
- $auth_rule_list = Db::name('auth_rule')->where('delete_time',0)->where(['status'=> 1,'ishidden'=>1])->order(['sort' => 'asc'])->select();
- //var_export($auth_rule_list);
+ $auth_rule_list = Db::name('auth_rule')->where(['delete_time'=> 0,'status'=> 1,'ismenu'=>1])->select();
foreach ($auth_rule_list as $value) {
if ($auth->check($value['name'], $admin_id) || $admin_id == 1) {
@@ -64,7 +63,7 @@ class AdminController extends \app\BaseController
}
}
- $menu = !empty($menu) ? array2tree($menu) : [];
+ $menu = !empty($menu) ? getTree($menu) : [];
View::assign('menu', $menu);
}
@@ -75,13 +74,13 @@ class AdminController extends \app\BaseController
protected function getMenus($type)
{
$menu = [];
- $auth_rule_list = Db::name('auth_rule')->where(['delete_time'=> 0, 'status'=> 1,'type'=> $type])->order(['sort' => 'ASC', 'id' => 'ASC'])->select();
+ $auth_rule_list = Db::name('auth_rule')->where(['delete_time'=> 0, 'status'=> 1,'type'=> $type])->select();
//var_export($auth_rule_list);
foreach ($auth_rule_list as $value) {
$menu[] = $value;
}
- $menus = !empty($menu) ? array2tree($menu) : [];
+ $menus = !empty($menu) ? getTree($menu) : [];
//$menu2 = getTree($menu);
return $menus;
//return View::assign('menus', $menus);
diff --git a/app/common/controller/BaseController.php b/app/common/controller/BaseController.php
index 7ccf188..5f015f9 100644
--- a/app/common/controller/BaseController.php
+++ b/app/common/controller/BaseController.php
@@ -67,12 +67,8 @@ class BaseController extends BaseCtrl
protected function showNav()
{
//1.查询分类表获取所有分类
- $cateList = Db::name('cate')->where(['status'=>1,'delete_time'=>0])->order(['id' => 'ASC','sort' => 'ASC'])->cache('catename',3600)->select()->toArray();
- $cateList = array2tree($cateList);
- // $cateList = getTree($cateList);
-
- return $cateList;
-
+ $cateList = Db::name('cate')->where(['status'=>1,'delete_time'=>0])->cache('catename',3600)->select()->toArray();
+ return getTree($cateList);
}
// 显示子导航subnav
@@ -86,7 +82,7 @@ class BaseController extends BaseCtrl
$subCateList = $this->showNav();
} else { // 点击分类,获取子分类信息
$parentId = $pCate['id'];
- $subCate = Db::name('cate')->field('id,ename,catename,is_hot,pid')->where(['pid'=>$parentId,'status'=>1,'delete_time'=>0])->order(['id' => 'ASC','sort' => 'ASC'])->select()->toArray();
+ $subCate = Db::name('cate')->field('id,ename,catename,is_hot,pid')->where(['pid'=>$parentId,'status'=>1,'delete_time'=>0])->select()->toArray();
if(!empty($subCate)) { // 有子分类
$subCateList = array2tree($subCate);
} else { //无子分类
diff --git a/app/common/lib/SqlFile.php b/app/common/lib/SqlFile.php
index 3c98fb6..c843ae2 100644
--- a/app/common/lib/SqlFile.php
+++ b/app/common/lib/SqlFile.php
@@ -13,21 +13,22 @@ declare (strict_types = 1);
namespace app\common\lib;
use think\facade\Lang;
+use think\facade\Db;
class SqlFile
{
+
+ protected static $path = null;
+
/**
* 加载sql文件为分号分割的数组
*
支持存储过程和函数提取,自动过滤注释
*
例如: var_export(load_sql_file('mysql_routing_example/fn_cdr_parse_accountcode.sql'));
* @param string $path 文件路径
- * @return boolean|array
- * @since 1.0 <2015-5-27> SoChishun Added.
+ * @return array
*/
- public function load_sql_file($path, $fn_splitor = ';;') {
- if (!file_exists($path)) {
- return false;
- }
+ public static function loadSqlFile(string $path, $fn_splitor = ';;') : array
+ {
$lines = file($path, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$aout = [];
$str = '';
@@ -72,4 +73,26 @@ class SqlFile
}
return $aout;
}
+
+ /**
+ * @param $sqlFile
+ * @return bool|void
+ * @throws \Exception
+ */
+ public static function dbExecute($sqlFile)
+ {
+ if (file_exists($sqlFile)) {
+ $sqlArr = self::loadSqlFile($sqlFile);
+ if(!empty($sqlArr)) {
+ foreach($sqlArr as $v){
+ try {
+ Db::execute($v);
+ } catch (\Exception $e) {
+ throw new \Exception($e->getMessage());
+ }
+ }
+ }
+ return true;
+ }
+ }
}
diff --git a/app/common/lib/Uploads.php b/app/common/lib/Uploads.php
index 223867a..7da85ce 100644
--- a/app/common/lib/Uploads.php
+++ b/app/common/lib/Uploads.php
@@ -70,7 +70,7 @@ class Uploads
* @param string $rule 文件命名规则,默认md5,uniqid,date,sha1,为空则取文件上传名称,或者自定义如a.jpg文件名
* @return \think\response\Json
*/
- public function put(string $fileName, string $dirName, int $fileSize, string $fileType, string $rule = null)
+ public function put(string $fileName, string $dirName, int $fileSize, string $fileType, string $rule = '')
{
if(stripos($fileName,'http') !== false) {
$file = $fileName;
@@ -90,7 +90,7 @@ class Uploads
} catch (ValidateException $e) {
return json(['status'=>-1,'msg'=>$e->getMessage()]);
}
- // 解析存储位置
+ // 解析存储位置 SYS_开头为系统位置
$isSys = stripos($dirName, 'SYS_');
if($isSys !== false) {
$disk = 'sys';
@@ -107,9 +107,9 @@ class Uploads
if(stripos($rule, '.') == false) {
$rule = $file->getOriginalName();
}
- $savename = \think\facade\Filesystem::disk($disk)->putFileAs($dirName, $file, $rule);
+ $savename = Filesystem::disk($disk)->putFileAs($dirName, $file, $rule);
} else {
- $savename = \think\facade\Filesystem::disk($disk)->putFile($dirName, $file, $rule);
+ $savename = Filesystem::disk($disk)->putFile($dirName, $file, $rule);
}
if($savename){
diff --git a/app/common/lib/Zip.php b/app/common/lib/Zip.php
index 1139cb7..4632cb6 100644
--- a/app/common/lib/Zip.php
+++ b/app/common/lib/Zip.php
@@ -10,8 +10,111 @@
*/
namespace app\common\lib;
+use think\Exception;
+
class Zip
{
+ /**
+ * 保持目录结构的压缩方法
+ * @param string $zipFile 压缩输出文件 相对或者绝对路径
+ * @param array|string $folderPaths 要压缩的目录 相对或者绝对路径
+ * @return void
+ */
+ public static function dirZip(string $zipFile, $folderPaths)
+ {
+ //1. $folderPaths 路径为数组
+ // 初始化zip对象
+ $zip = new \ZipArchive();
+ //打开压缩文件
+ $zip->open($zipFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
+
+ if(is_array($folderPaths)) {
+ foreach($folderPaths as $folderPath) {
+ if(self::getDirSize($folderPath) == 0) {
+ continue;
+ };
+ // 被压缩文件绝对路径
+ $rootPath = realpath($folderPath);
+ // Create recursive directory iterator
+ //获取所有文件数组SplFileInfo[] $files
+ $files = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($rootPath),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($files as $name => $file) {
+ //要跳过所有子目录 Skip directories (they would be added automatically)
+ if (!$file->isDir()) {
+ // 真实文件路径
+ $filePath = $file->getRealPath();
+ // zip文件的相对路径
+ $relativePath = str_replace('\\','/',str_replace(root_path(), '', $filePath));
+ //添加文件到压缩包
+ $zip->addFile($filePath, $relativePath);
+ }
+ }
+ }
+ } else {
+ // 2. $folderPaths 路径为string
+ if(self::getDirSize($folderPaths) == 0) {
+ throw new \Exception("Directory name must not be empty.");
+ };
+ // 被压缩文件绝对路径
+ $rootPath = realpath($folderPaths);
+ // Create recursive directory iterator
+ //获取所有文件数组SplFileInfo[] $files
+ $files = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($rootPath),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ foreach ($files as $name => $file) {
+ //要跳过所有子目录 Skip directories (they would be added automatically)
+ if (!$file->isDir()) {
+ // 要压缩的文件路径
+ $filePath = $file->getRealPath();
+ // zip目录内文件的相对路径
+ $relativePath = str_replace('\\','/',str_replace(root_path(), '', $filePath));
+ //添加文件到压缩包
+ $zip->addFile($filePath, $relativePath);
+ }
+ }
+ }
+
+ $zip->close();
+ }
+
+ /**
+ * 把目录内所有文件进行压缩输出
+ * @param string $zipFile 压缩文件保存路径 相对路径或者绝对路径
+ * @param string $folderPath 要压缩的目录 相对路径或者绝对路径
+ * @return void
+ */
+ public static function zipDir(string $zipFile, string $folderPath)
+ {
+ // 初始化zip对象
+ $zip = new \ZipArchive();
+ $zip->open($zipFile, \ZipArchive::CREATE | \ZipArchive::OVERWRITE);
+
+ $rootPath = realpath($folderPath);
+ $files = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($rootPath),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+ foreach ($files as $name => $file) {
+ if (!$file->isDir()) {
+ // 要压缩的文件路径
+ $filePath = $file->getRealPath();
+ // zip目录内文件的相对路径
+ $relativePath = substr($filePath, strlen($rootPath) + 1);
+ // 添加 文件 到 压缩包
+ $zip->addFile($filePath, $relativePath);
+ }
+ }
+
+ $zip->close();
+ }
+
/**
* 压缩文件
* @param array $files 待压缩文件 array('d:/test/1.txt','d:/test/2.jpg');【文件地址为绝对路径】
@@ -49,9 +152,7 @@ class Zip
if (empty($path) || empty($filePath)) {
return false;
}
-
$zip = new \ZipArchive();
-
if ($zip->open($filePath) === true) {
$zip->extractTo($path);
$zip->close();
@@ -60,5 +161,30 @@ class Zip
return false;
}
}
+
+ /**
+ * 获取文件夹大小
+ * @param $dir 根文件夹路径
+ * @return bool|int
+ */
+ public static function getDirSize($dir)
+ {
+ if(!is_dir($dir)){
+ return false;
+ }
+ $handle = opendir($dir);
+ $sizeResult = 0;
+ while (false !== ($FolderOrFile = readdir($handle))) {
+ if ($FolderOrFile != "." && $FolderOrFile != "..") {
+ if (is_dir("$dir/$FolderOrFile")) {
+ $sizeResult += self::getDirSize("$dir/$FolderOrFile");
+ } else {
+ $sizeResult += filesize("$dir/$FolderOrFile");
+ }
+ }
+ }
+
+ closedir($handle);
+ return $sizeResult;
+ }
}
-?>
\ No newline at end of file
diff --git a/app/common/model/Article.php b/app/common/model/Article.php
index 4751116..2ef5b2e 100644
--- a/app/common/model/Article.php
+++ b/app/common/model/Article.php
@@ -111,7 +111,8 @@ class Article extends Model
{
$artTop = Cache::get('arttop');
if (!$artTop) {
- $artTop = $this::field('id,title,title_color,cate_id,user_id,create_time,is_top,pv,jie,upzip,has_img,has_video,has_audio')->where(['is_top' => 1, 'status' => 1])->with([
+ $artTop = $this::field('id,title,title_color,cate_id,user_id,create_time,is_top,pv,jie,upzip,has_img,has_video,has_audio')->where(['is_top' => 1, 'status' => 1])
+ ->with([
'cate' => function ($query) {
$query->where('delete_time', 0)->field('id,catename,ename');
},
diff --git a/app/common/model/Cate.php b/app/common/model/Cate.php
index 2e3b33e..8b44dd2 100644
--- a/app/common/model/Cate.php
+++ b/app/common/model/Cate.php
@@ -30,14 +30,14 @@ class Cate extends Model
public function getCateInfo(string $ename)
{
//
- return $this::field('ename,catename,detpl,desc')->where('ename',$ename)->cache('cate_'.$ename,600)->find();
+ return $this->field('ename,catename,detpl,desc')->where('ename',$ename)->cache('cate_'.$ename,600)->find();
}
// 删除类别
public function del($id)
{
- $cates = $this::field('id,pid')->with('article')->find($id);
- $sonCate = $this::field('id,pid')->where('pid',$cates['id'])->find();
+ $cates = $this->field('id,pid')->with('article')->find($id);
+ $sonCate = $this->field('id,pid')->where('pid',$cates['id'])->find();
if(empty($sonCate)) {
$res = $cates->together(['article'])->delete();
if($res){
@@ -48,9 +48,21 @@ class Cate extends Model
} else {
return '存在子分类,无法删除';
}
-
-
}
+
+ // 分类表
+ public function getList()
+ {
+ $data = $this->field('sort,id,pid,catename,ename,detpl,icon,is_hot,desc')->where(['status'=>1])->select()->toArray();
+ // 排序
+ $cmf_arr = array_column($data, 'sort');
+ array_multisort($cmf_arr, SORT_ASC, $data);
+ if(count($data)) {
+ return json(['code'=>0,'msg'=>'ok','data'=>$data]);
+ } else {
+ return json(['code'=>-1,'msg'=>'no data','data'=>'']);
+ }
+ }
}
\ No newline at end of file
diff --git a/app/common/model/Comment.php b/app/common/model/Comment.php
index 4a61339..f54d1b7 100644
--- a/app/common/model/Comment.php
+++ b/app/common/model/Comment.php
@@ -80,13 +80,13 @@ class Comment extends Model
* @param integer $id
* @return void
*/
- public function getUserCommentList(int $id) {
- $userCommList = $this::field('id,user_id,create_time,delete_time,article_id,content')
- ->with(['article' => function(\think\model\Relation $query){
- $query->withField('id,title,cate_id,delete_time')->where(['status' => 1]);
+ public function getUserCommentList1(int $id) {
+ $userCommList = $this::field('id,user_id,create_time,article_id,content')
+ ->with(['article' => function($query){
+ $query->withField('id,title,create_time')->where(['delete_time'=>0,'status' => 1]);
}])
->where(['user_id' => $id,'status' => 1])
- //->append(['url'])
+ ->append(['url'])
->order(['create_time' => 'desc'])
//->cache(3600)
->select()
@@ -95,12 +95,39 @@ class Comment extends Model
return $userCommList;
}
+ /**
+ * 获取用户评论列表
+ *
+ * @param integer $id
+ * @return void
+ */
+ public function getUserCommentList(int $id) {
+ $userCommList = Article::field('Article.id,title,Article.create_time')->hasWhere('comments',['status'=>1,'delete_time'=>0])
+ ->with(['comments' => function($query) use($id){
+ $query->withField('id,content')->where(['user_id'=>$id,'delete_time'=>0,'status' => 1]);
+ }])
+ ->append(['url'])
+ ->order(['create_time' => 'desc'])
+ //->cache(3600)
+ ->select()
+ ->toArray();
+
+ return $userCommList;
+ }
+
+
+
// 获取url
public function getUrlAttr($value,$data)
{
+ // dump($data);
if(config('taoler.url_rewrite.article_as') == '
/') {
- $cate = Cate::field('id,ename')->find($data['article']['cate_id']);
- return (string) url('detail',['id' => $data['id'],'ename'=>$cate->ename]);
+
+ $article = Article::field('id,cate_id')->with(['cate' => function($query){
+ $query->withField('id,ename')->where(['status' => 1]);
+ }])->find($data['article_id']);
+
+ return (string) url('detail',['id' => $data['article_id'],'ename'=>$article->cate->ename]);
} else {
return (string) url('detail',['id' => $data['id']]);
}
diff --git a/app/index/controller/User.php b/app/index/controller/User.php
index ef09a3b..2a4e5ba 100644
--- a/app/index/controller/User.php
+++ b/app/index/controller/User.php
@@ -207,21 +207,22 @@ class User extends BaseController
}
$article = new Article();
- // $commont = new Comment();
$arts = $article->getUserArtList((int) $id);
- // $reys = $commont->getUserCommentList((int) $id);
- // dump($reys);
//用户回答
+ // $commont = new Comment();
+ // $reys = $commont->getUserCommentList((int) $id);
+
$reys = Db::name('comment')
->alias('c')
->join('article a','c.article_id = a.id')
- ->field('a.id,a.title,c.content,c.create_time,c.delete_time,c.status')
+ ->join('cate t','a.cate_id = t.id')
+ ->field('a.id,a.title,t.ename,c.content,c.create_time,c.delete_time,c.status')
->where(['a.delete_time'=>0,'c.delete_time'=>0,'c.status'=>1])
->where('c.user_id',$id)
->order(['c.create_time'=>'desc'])
->cache(3600)->select();
-
+
View::assign(['u'=>$u,'arts'=>$arts,'reys'=>$reys,'jspage'=>'']);
return View::fetch();
}
diff --git a/app/install/common.php b/app/install/common.php
index 531028b..9688798 100644
--- a/app/install/common.php
+++ b/app/install/common.php
@@ -1,12 +1,5 @@
-// +----------------------------------------------------------------------
// 检测环境是否支持可写
//define('IS_WRITE', true);
@@ -241,7 +234,7 @@ function strReplace($find,$replace,$array){
foreach ($array as $key => $val) {
- if (is_array($val)) $array[$key]=$this->strReplace($find,$replace,$array[$key]);
+ if (is_array($val)) $array[$key] = $this->strReplace($find,$replace,$array[$key]);
}
diff --git a/app/install/data/taoler.sql b/app/install/data/taoler.sql
index ea2d8db..fe2e805 100644
--- a/app/install/data/taoler.sql
+++ b/app/install/data/taoler.sql
@@ -162,12 +162,12 @@ CREATE TABLE `tao_auth_rule` (
`title` char(20) NOT NULL DEFAULT '' COMMENT '权限标题',
`etitle` varchar(100) NOT NULL DEFAULT '' COMMENT '英文权限标题',
`type` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '类型',
- `status` enum('1','0') NOT NULL DEFAULT '1' COMMENT '菜单1启用,0禁用',
+ `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1启用,0禁用',
`pid` smallint(5) NOT NULL DEFAULT '0' COMMENT '父级ID',
`level` tinyint(1) NOT NULL DEFAULT '1' COMMENT '菜单层级',
`icon` varchar(50) NOT NULL DEFAULT '' COMMENT '图标',
- `ishidden` enum('1','0','-1') NOT NULL DEFAULT '1' COMMENT '0隐藏,1显示-1其它',
- `sort` tinyint(4) NOT NULL DEFAULT '50' COMMENT '排序',
+ `ismenu` tinyint(1) NOT NULL DEFAULT 1 COMMENT '0目录,1菜单2按钮',
+ `sort` int(10) NOT NULL DEFAULT '50' COMMENT '排序',
`condition` char(100) NOT NULL DEFAULT '',
`create_time` int(11) NOT NULL DEFAULT '0' COMMENT '创建时间',
`update_time` int(11) NOT NULL DEFAULT '0' COMMENT '更新时间',
diff --git a/composer.json b/composer.json
index 2b534fb..81f9d19 100644
--- a/composer.json
+++ b/composer.json
@@ -36,7 +36,8 @@
"yansongda/pay": "~3.1.0",
"guzzlehttp/guzzle": "7.0",
"php-di/php-di": "^6.4",
- "workerman/phpsocket.io": "^1.1"
+ "workerman/phpsocket.io": "^1.1",
+ "jaeger/querylist": "^4.2"
},
"require-dev": {
"symfony/var-dumper": "^4.2",
diff --git a/composer.lock b/composer.lock
index e8ebe0f..58105fa 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "ae8b0fc745366cac28cf9550ec737472",
+ "content-hash": "c80928616d71c7770ef136acd370770e",
"packages": [
{
"name": "bacon/bacon-qr-code",
@@ -60,6 +60,201 @@
},
"time": "2022-03-14T02:02:36+00:00"
},
+ {
+ "name": "cache/adapter-common",
+ "version": "1.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-cache/adapter-common.git",
+ "reference": "8788309be72aa7be69b88cdc0687549c74a7d479"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-cache/adapter-common/zipball/8788309be72aa7be69b88cdc0687549c74a7d479",
+ "reference": "8788309be72aa7be69b88cdc0687549c74a7d479",
+ "shasum": ""
+ },
+ "require": {
+ "cache/tag-interop": "^1.0",
+ "php": ">=7.4",
+ "psr/cache": "^1.0 || ^2.0",
+ "psr/log": "^1.0 || ^2.0 || ^3.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "^0.17",
+ "phpunit/phpunit": "^7.5.20 || ^9.5.10"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Cache\\Adapter\\Common\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Scherer",
+ "email": "aequasi@gmail.com",
+ "homepage": "https://github.com/aequasi"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ }
+ ],
+ "description": "Common classes for PSR-6 adapters",
+ "homepage": "http://www.php-cache.com/en/latest/",
+ "keywords": [
+ "cache",
+ "psr-6",
+ "tag"
+ ],
+ "support": {
+ "source": "https://github.com/php-cache/adapter-common/tree/1.3.0"
+ },
+ "time": "2022-01-15T15:47:19+00:00"
+ },
+ {
+ "name": "cache/filesystem-adapter",
+ "version": "1.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-cache/filesystem-adapter.git",
+ "reference": "f1faaae40aaa696ef899cef6f6888aedb90b419b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-cache/filesystem-adapter/zipball/f1faaae40aaa696ef899cef6f6888aedb90b419b",
+ "reference": "f1faaae40aaa696ef899cef6f6888aedb90b419b",
+ "shasum": ""
+ },
+ "require": {
+ "cache/adapter-common": "^1.0",
+ "league/flysystem": "^1.0",
+ "php": ">=7.4",
+ "psr/cache": "^1.0 || ^2.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "provide": {
+ "psr/cache-implementation": "^1.0",
+ "psr/simple-cache-implementation": "^1.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "^0.17",
+ "phpunit/phpunit": "^7.5.20 || ^9.5.10"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Cache\\Adapter\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Scherer",
+ "email": "aequasi@gmail.com",
+ "homepage": "https://github.com/aequasi"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ }
+ ],
+ "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags",
+ "homepage": "http://www.php-cache.com/en/latest/",
+ "keywords": [
+ "cache",
+ "filesystem",
+ "psr-6",
+ "tag"
+ ],
+ "support": {
+ "source": "https://github.com/php-cache/filesystem-adapter/tree/1.2.0"
+ },
+ "time": "2022-01-15T15:47:19+00:00"
+ },
+ {
+ "name": "cache/tag-interop",
+ "version": "1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-cache/tag-interop.git",
+ "reference": "b062b1d735357da50edf8387f7a8696f3027d328"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-cache/tag-interop/zipball/b062b1d735357da50edf8387f7a8696f3027d328",
+ "reference": "b062b1d735357da50edf8387f7a8696f3027d328",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5 || ^7.0 || ^8.0",
+ "psr/cache": "^1.0 || ^2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Cache\\TagInterop\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com",
+ "homepage": "https://github.com/nicolas-grekas"
+ }
+ ],
+ "description": "Framework interoperable interfaces for tags",
+ "homepage": "https://www.php-cache.com/en/latest/",
+ "keywords": [
+ "cache",
+ "psr",
+ "psr6",
+ "tag"
+ ],
+ "support": {
+ "issues": "https://github.com/php-cache/tag-interop/issues",
+ "source": "https://github.com/php-cache/tag-interop/tree/1.1.0"
+ },
+ "time": "2021-12-31T10:03:23+00:00"
+ },
{
"name": "dasprid/enum",
"version": "1.0.3",
@@ -515,6 +710,154 @@
],
"time": "2022-06-20T21:43:03+00:00"
},
+ {
+ "name": "jaeger/g-http",
+ "version": "V1.7.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jae-jae/GHttp.git",
+ "reference": "82585ddd5e2c6651e37ab1d8166efcdbb6b293d4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jae-jae/GHttp/zipball/82585ddd5e2c6651e37ab1d8166efcdbb6b293d4",
+ "reference": "82585ddd5e2c6651e37ab1d8166efcdbb6b293d4",
+ "shasum": ""
+ },
+ "require": {
+ "cache/filesystem-adapter": "^1",
+ "guzzlehttp/guzzle": "^6.0 | ^7.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Jaeger\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaeger",
+ "email": "JaegerCode@gmail.com"
+ }
+ ],
+ "description": "Simple Http client base on GuzzleHttp",
+ "support": {
+ "issues": "https://github.com/jae-jae/GHttp/issues",
+ "source": "https://github.com/jae-jae/GHttp/tree/V1.7.2"
+ },
+ "time": "2021-08-08T04:59:44+00:00"
+ },
+ {
+ "name": "jaeger/phpquery-single",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jae-jae/phpQuery-single.git",
+ "reference": "39a650ade692a6b480c22220dce0c198d6a946fb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jae-jae/phpQuery-single/zipball/39a650ade692a6b480c22220dce0c198d6a946fb",
+ "reference": "39a650ade692a6b480c22220dce0c198d6a946fb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "phpQuery.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tobiasz Cudnik",
+ "email": "tobiasz.cudnik@gmail.com",
+ "homepage": "https://github.com/TobiaszCudnik",
+ "role": "Developer"
+ },
+ {
+ "name": "Jaeger",
+ "role": "Packager"
+ }
+ ],
+ "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/",
+ "homepage": "http://code.google.com/p/phpquery/",
+ "support": {
+ "issues": "https://github.com/jae-jae/phpQuery-single/issues",
+ "source": "https://github.com/jae-jae/phpQuery-single/tree/1.1.1"
+ },
+ "time": "2022-03-26T15:01:16+00:00"
+ },
+ {
+ "name": "jaeger/querylist",
+ "version": "V4.2.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jae-jae/QueryList.git",
+ "reference": "39dc0ca9c668bec7a793e20472ccd7d26ef89ea4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jae-jae/QueryList/zipball/39dc0ca9c668bec7a793e20472ccd7d26ef89ea4",
+ "reference": "39dc0ca9c668bec7a793e20472ccd7d26ef89ea4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "jaeger/g-http": "^1.1",
+ "jaeger/phpquery-single": "^1",
+ "php": ">=7.1",
+ "tightenco/collect": ">5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5",
+ "symfony/var-dumper": "^3.3"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "QL\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaeger",
+ "email": "JaegerCode@gmail.com"
+ }
+ ],
+ "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。",
+ "homepage": "http://querylist.cc",
+ "keywords": [
+ "QueryList",
+ "phpQuery",
+ "spider"
+ ],
+ "support": {
+ "issues": "https://github.com/jae-jae/QueryList/issues",
+ "source": "https://github.com/jae-jae/QueryList/tree/V4.2.8"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/querylist",
+ "type": "open_collective"
+ }
+ ],
+ "time": "2021-07-05T06:07:58+00:00"
+ },
{
"name": "laravel/serializable-closure",
"version": "v1.2.2",
@@ -1543,6 +1886,337 @@
},
"time": "2019-03-08T08:55:37+00:00"
},
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.26.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
+ "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.26-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-05-24T11:49:31+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php72",
+ "version": "v1.26.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php72.git",
+ "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2",
+ "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.26-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php72\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-05-24T11:49:31+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.26.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
+ "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "1.26-dev"
+ },
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-05-10T07:21:04+00:00"
+ },
+ {
+ "name": "symfony/var-dumper",
+ "version": "v4.4.44",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/var-dumper.git",
+ "reference": "f19951007dae942cc79b979c1fe26bfdfbeb54ed"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f19951007dae942cc79b979c1fe26bfdfbeb54ed",
+ "reference": "f19951007dae942cc79b979c1fe26bfdfbeb54ed",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1.3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/polyfill-php72": "~1.5",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "conflict": {
+ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
+ "symfony/console": "<3.4"
+ },
+ "require-dev": {
+ "ext-iconv": "*",
+ "symfony/console": "^3.4|^4.0|^5.0",
+ "symfony/process": "^4.4|^5.0",
+ "twig/twig": "^1.43|^2.13|^3.0.4"
+ },
+ "suggest": {
+ "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
+ "ext-intl": "To show region name in time zone dump",
+ "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
+ },
+ "bin": [
+ "Resources/bin/var-dump-server"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "Resources/functions/dump.php"
+ ],
+ "psr-4": {
+ "Symfony\\Component\\VarDumper\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides mechanisms for walking through any arbitrary PHP variable",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "debug",
+ "dump"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/var-dumper/tree/v4.4.44"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2022-07-20T09:59:04+00:00"
+ },
{
"name": "taoser/think-addons",
"version": "v1.0.3",
@@ -1692,6 +2366,60 @@
},
"time": "2022-04-16T23:08:43+00:00"
},
+ {
+ "name": "tightenco/collect",
+ "version": "v8.83.23",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/tighten/collect.git",
+ "reference": "a4423c6ace6b54ba4f86c0ac9de588c57bc94d79"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/tighten/collect/zipball/a4423c6ace6b54ba4f86c0ac9de588c57bc94d79",
+ "reference": "a4423c6ace6b54ba4f86c0ac9de588c57bc94d79",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3|^8.0",
+ "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "nesbot/carbon": "^2.23.0",
+ "phpunit/phpunit": "^8.3"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/Collect/Support/helpers.php",
+ "src/Collect/Support/alias.php"
+ ],
+ "psr-4": {
+ "Tightenco\\Collect\\": "src/Collect"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylorotwell@gmail.com"
+ }
+ ],
+ "description": "Collect - Illuminate Collections as a separate package.",
+ "keywords": [
+ "collection",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/tighten/collect/issues",
+ "source": "https://github.com/tighten/collect/tree/v8.83.23"
+ },
+ "time": "2022-08-22T17:50:04+00:00"
+ },
{
"name": "topthink/framework",
"version": "v6.0.13",
@@ -2472,337 +3200,6 @@
}
],
"packages-dev": [
- {
- "name": "symfony/polyfill-mbstring",
- "version": "v1.26.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
- "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
- "shasum": ""
- },
- "require": {
- "php": ">=7.1"
- },
- "provide": {
- "ext-mbstring": "*"
- },
- "suggest": {
- "ext-mbstring": "For best performance"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-main": "1.26-dev"
- },
- "thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for the Mbstring extension",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "mbstring",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2022-05-24T11:49:31+00:00"
- },
- {
- "name": "symfony/polyfill-php72",
- "version": "v1.26.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-php72.git",
- "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2",
- "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2",
- "shasum": ""
- },
- "require": {
- "php": ">=7.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-main": "1.26-dev"
- },
- "thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Php72\\": ""
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-php72/tree/v1.26.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2022-05-24T11:49:31+00:00"
- },
- {
- "name": "symfony/polyfill-php80",
- "version": "v1.26.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-php80.git",
- "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
- "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
- "shasum": ""
- },
- "require": {
- "php": ">=7.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-main": "1.26-dev"
- },
- "thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
- }
- },
- "autoload": {
- "files": [
- "bootstrap.php"
- ],
- "psr-4": {
- "Symfony\\Polyfill\\Php80\\": ""
- },
- "classmap": [
- "Resources/stubs"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Ion Bazan",
- "email": "ion.bazan@gmail.com"
- },
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "polyfill",
- "portable",
- "shim"
- ],
- "support": {
- "source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2022-05-10T07:21:04+00:00"
- },
- {
- "name": "symfony/var-dumper",
- "version": "v4.4.44",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/var-dumper.git",
- "reference": "f19951007dae942cc79b979c1fe26bfdfbeb54ed"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/var-dumper/zipball/f19951007dae942cc79b979c1fe26bfdfbeb54ed",
- "reference": "f19951007dae942cc79b979c1fe26bfdfbeb54ed",
- "shasum": ""
- },
- "require": {
- "php": ">=7.1.3",
- "symfony/polyfill-mbstring": "~1.0",
- "symfony/polyfill-php72": "~1.5",
- "symfony/polyfill-php80": "^1.16"
- },
- "conflict": {
- "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
- "symfony/console": "<3.4"
- },
- "require-dev": {
- "ext-iconv": "*",
- "symfony/console": "^3.4|^4.0|^5.0",
- "symfony/process": "^4.4|^5.0",
- "twig/twig": "^1.43|^2.13|^3.0.4"
- },
- "suggest": {
- "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
- "ext-intl": "To show region name in time zone dump",
- "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
- },
- "bin": [
- "Resources/bin/var-dump-server"
- ],
- "type": "library",
- "autoload": {
- "files": [
- "Resources/functions/dump.php"
- ],
- "psr-4": {
- "Symfony\\Component\\VarDumper\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Provides mechanisms for walking through any arbitrary PHP variable",
- "homepage": "https://symfony.com",
- "keywords": [
- "debug",
- "dump"
- ],
- "support": {
- "source": "https://github.com/symfony/var-dumper/tree/v4.4.44"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2022-07-20T09:59:04+00:00"
- },
{
"name": "topthink/think-trace",
"version": "v1.4",
diff --git a/config/taoler.php b/config/taoler.php
index 70424c1..2a1ffd6 100644
--- a/config/taoler.php
+++ b/config/taoler.php
@@ -16,7 +16,7 @@ return [
// 应用名,此项不可更改
'appname' => 'TaoLer',
// 版本配置
- 'version' => '1.9.29',
+ 'version' => '2.0.0',
// 加盐
'salt' => 'taoler',
// 数据库备份目录
diff --git a/extend/taoler/com/Api.php b/extend/taoler/com/Api.php
index 6102875..21604d8 100644
--- a/extend/taoler/com/Api.php
+++ b/extend/taoler/com/Api.php
@@ -10,6 +10,7 @@
*/
namespace taoler\com;
+use think\Response;
class Api
{
@@ -50,7 +51,6 @@ class Api
curl_close($ch);
if($httpCode == '200'){
return json_decode($data);
- //return $data;
} else {
//$status ='{"code":-1,"msg":"远程服务器失败"}'; //字符串
return json_decode('{"code":-1,"msg":"远程服务器失败,稍后重试"}'); //转换为对象
diff --git a/extend/taoler/com/Files.php b/extend/taoler/com/Files.php
index 4602607..542b470 100644
--- a/extend/taoler/com/Files.php
+++ b/extend/taoler/com/Files.php
@@ -3,6 +3,7 @@
namespace taoler\com;
use RecursiveIteratorIterator;
use RecursiveDirectoryIterator;
+
class Files
{
/**
@@ -12,7 +13,8 @@ class Files
*/
public static function getDirPath($path)
{
- return substr($path,-1) == '/' ? $path : $path.'/';
+ //去掉path最右侧的/号,再重新组装带/路径
+ return rtrim($path,'/') . '/';
}
/**
@@ -118,27 +120,22 @@ class Files
public static function delDirAndFile(string $dirPath, $nowDir=false )
{
if(!is_dir($dirPath)) return 'dir not exist';
- if ( $handle = opendir($dirPath) ) {
-
- while ( false !== ( $item = readdir( $handle ) ) ) {
- if ( $item != '.' && $item != '..' ) {
+ if ( $handle = opendir($dirPath) ) {
+ while ( false !== ( $item = readdir( $handle ) ) ) {
+ if ( $item != '.' && $item != '..' ) {
$path = $dirPath.$item;
- if (is_dir($path)) {
- self::delDirAndFile($path.'/');
+ if (is_dir($path)) {
+ self::delDirAndFile($path.'/');
rmdir($path.'/');
- } else {
- unlink($path);
- }
- }
- }
+ } else {
+ unlink($path);
+ }
+ }
+ }
closedir( $handle );
//删除当前文件夹
if($nowDir == true){
- if(rmdir($dirPath)){
- return true;
- } else {
- return false;
- }
+ if(!rmdir($dirPath)) return false;
}
} else {
return false;
diff --git a/public/static/admin/modules/forum.js b/public/static/admin/modules/forum.js
index 7dcb1ec..37046ad 100644
--- a/public/static/admin/modules/forum.js
+++ b/public/static/admin/modules/forum.js
@@ -13,7 +13,7 @@ var forms = table.render({
,{field: 'poster', title: '账号',width: 80}
,{field: 'avatar', title: '头像', width: 60, templet: '#avatarTpl'}
,{field: 'title', title: '标题', minWidth: 180,templet: ''}
- ,{field: 'content', title: '内容', templet: '{{= d.content }}
', minWidth: 200}
+ ,{field: 'content', title: '内容', 'escape':false, minWidth: 200}
,{field: 'posttime', title: '时间',width: 120, sort: true}
,{field: 'top', title: '置顶', templet: '#buttonTpl', width: 80, align: 'center'}
,{field: 'hot', title: '加精', templet: '#buttonHot', width: 80, align: 'center'}
diff --git a/public/static/res/mods/index.js b/public/static/res/mods/index.js
index 7d355be..368d94d 100644
--- a/public/static/res/mods/index.js
+++ b/public/static/res/mods/index.js
@@ -995,7 +995,7 @@ layui.define(['layer', 'laytpl', 'form', 'element', 'upload', 'util', 'imgcom'],
util.fixbar({
bar1: ''
,bgcolor: '#009688'
- ,css: {right: 10, bottom: 100}
+ ,css: {right: 10, bottom: 50}
,click: function(type){
//添加文章
if(type === 'bar1'){
diff --git a/runtime/update.sql b/runtime/update.sql
index 8d42879..c69205e 100644
--- a/runtime/update.sql
+++ b/runtime/update.sql
@@ -1,25 +1,2 @@
-ALTER TABLE `tao_push_jscode` ADD `type` tinyint(1) NOT NULL DEFAULT '1' COMMENT '1push2taglink' AFTER `jscode`;
-
-DROP TABLE IF EXISTS `tao_tag`;
-CREATE TABLE `tao_tag` (
- `id` int(10) NOT NULL AUTO_INCREMENT COMMENT 'tag自增id',
- `name` varchar(20) NOT NULL COMMENT '名称',
- `ename` varchar(20) NOT NULL COMMENT '英文名',
- `create_time` int NOT NULL COMMENT '创建时间',
- `update_time` int NOT NULL COMMENT '更新时间',
- PRIMARY KEY (`id`),
- KEY `ename` (`ename`) USING BTREE COMMENT 'ename查询tag索引'
-) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='文章tag表';
-
-DROP TABLE IF EXISTS `tao_taglist`;
-CREATE TABLE `tao_taglist` (
- `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '标签列表id',
- `tag_id` int NOT NULL COMMENT '标签id',
- `article_id` int NOT NULL COMMENT '文章id',
- `create_time` int NOT NULL COMMENT '创建时间',
- PRIMARY KEY (`id`),
- KEY `tag_id` (`tag_id`) USING BTREE COMMENT 'tagID索引',
- KEY `article_id` (`article_id`) USING BTREE COMMENT '文章ID查询索引'
-) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='tag详细列表';
-
-ALTER TABLE `tao_article` CHANGE `tags` `keywords` varchar(255) DEFAULT NULL COMMENT '关键词';
\ No newline at end of file
+ALTER TABLE `tao_auth_rule` CHANGE `sort` `sort` int NOT NULL DEFAULT 50 COMMENT '排序';
+ALTER TABLE `tao_auth_rule` CHANGE `ishidden` `ismenu` enum('1','2','3','0','-1') NOT NULL DEFAULT '1' COMMENT '1菜单,2按钮3目录';
\ No newline at end of file
diff --git a/vendor/cache/adapter-common/AbstractCachePool.php b/vendor/cache/adapter-common/AbstractCachePool.php
new file mode 100644
index 0000000..de7d2bc
--- /dev/null
+++ b/vendor/cache/adapter-common/AbstractCachePool.php
@@ -0,0 +1,559 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common;
+
+use Cache\Adapter\Common\Exception\CacheException;
+use Cache\Adapter\Common\Exception\CachePoolException;
+use Cache\Adapter\Common\Exception\InvalidArgumentException;
+use Psr\Cache\CacheItemInterface;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\SimpleCache\CacheInterface;
+
+/**
+ * @author Aaron Scherer
+ * @author Tobias Nyholm
+ */
+abstract class AbstractCachePool implements PhpCachePool, LoggerAwareInterface, CacheInterface
+{
+ const SEPARATOR_TAG = '!';
+
+ /**
+ * @type LoggerInterface
+ */
+ private $logger;
+
+ /**
+ * @type PhpCacheItem[] deferred
+ */
+ protected $deferred = [];
+
+ /**
+ * @param PhpCacheItem $item
+ * @param int|null $ttl seconds from now
+ *
+ * @return bool true if saved
+ */
+ abstract protected function storeItemInCache(PhpCacheItem $item, $ttl);
+
+ /**
+ * Fetch an object from the cache implementation.
+ *
+ * If it is a cache miss, it MUST return [false, null, [], null]
+ *
+ * @param string $key
+ *
+ * @return array with [isHit, value, tags[], expirationTimestamp]
+ */
+ abstract protected function fetchObjectFromCache($key);
+
+ /**
+ * Clear all objects from cache.
+ *
+ * @return bool false if error
+ */
+ abstract protected function clearAllObjectsFromCache();
+
+ /**
+ * Remove one object from cache.
+ *
+ * @param string $key
+ *
+ * @return bool
+ */
+ abstract protected function clearOneObjectFromCache($key);
+
+ /**
+ * Get an array with all the values in the list named $name.
+ *
+ * @param string $name
+ *
+ * @return array
+ */
+ abstract protected function getList($name);
+
+ /**
+ * Remove the list.
+ *
+ * @param string $name
+ *
+ * @return bool
+ */
+ abstract protected function removeList($name);
+
+ /**
+ * Add a item key on a list named $name.
+ *
+ * @param string $name
+ * @param string $key
+ */
+ abstract protected function appendListItem($name, $key);
+
+ /**
+ * Remove an item from the list.
+ *
+ * @param string $name
+ * @param string $key
+ */
+ abstract protected function removeListItem($name, $key);
+
+ /**
+ * Make sure to commit before we destruct.
+ */
+ public function __destruct()
+ {
+ $this->commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItem($key)
+ {
+ $this->validateKey($key);
+ if (isset($this->deferred[$key])) {
+ /** @type CacheItem $item */
+ $item = clone $this->deferred[$key];
+ $item->moveTagsToPrevious();
+
+ return $item;
+ }
+
+ $func = function () use ($key) {
+ try {
+ return $this->fetchObjectFromCache($key);
+ } catch (\Exception $e) {
+ $this->handleException($e, __FUNCTION__);
+ }
+ };
+
+ return new CacheItem($key, $func);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getItems(array $keys = [])
+ {
+ $items = [];
+ foreach ($keys as $key) {
+ $items[$key] = $this->getItem($key);
+ }
+
+ return $items;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function hasItem($key)
+ {
+ try {
+ return $this->getItem($key)->isHit();
+ } catch (\Exception $e) {
+ $this->handleException($e, __FUNCTION__);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ // Clear the deferred items
+ $this->deferred = [];
+
+ try {
+ return $this->clearAllObjectsFromCache();
+ } catch (\Exception $e) {
+ $this->handleException($e, __FUNCTION__);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteItem($key)
+ {
+ try {
+ return $this->deleteItems([$key]);
+ } catch (\Exception $e) {
+ $this->handleException($e, __FUNCTION__);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteItems(array $keys)
+ {
+ $deleted = true;
+ foreach ($keys as $key) {
+ $this->validateKey($key);
+
+ // Delete form deferred
+ unset($this->deferred[$key]);
+
+ // We have to commit here to be able to remove deferred hierarchy items
+ $this->commit();
+ $this->preRemoveItem($key);
+
+ if (!$this->clearOneObjectFromCache($key)) {
+ $deleted = false;
+ }
+ }
+
+ return $deleted;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function save(CacheItemInterface $item)
+ {
+ if (!$item instanceof PhpCacheItem) {
+ $e = new InvalidArgumentException('Cache items are not transferable between pools. Item MUST implement PhpCacheItem.');
+ $this->handleException($e, __FUNCTION__);
+ }
+
+ $this->removeTagEntries($item);
+ $this->saveTags($item);
+ $timeToLive = null;
+ if (null !== $timestamp = $item->getExpirationTimestamp()) {
+ $timeToLive = $timestamp - time();
+
+ if ($timeToLive < 0) {
+ return $this->deleteItem($item->getKey());
+ }
+ }
+
+ try {
+ return $this->storeItemInCache($item, $timeToLive);
+ } catch (\Exception $e) {
+ $this->handleException($e, __FUNCTION__);
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function saveDeferred(CacheItemInterface $item)
+ {
+ $this->deferred[$item->getKey()] = $item;
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function commit()
+ {
+ $saved = true;
+ foreach ($this->deferred as $item) {
+ if (!$this->save($item)) {
+ $saved = false;
+ }
+ }
+ $this->deferred = [];
+
+ return $saved;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @throws InvalidArgumentException
+ */
+ protected function validateKey($key)
+ {
+ if (!is_string($key)) {
+ $e = new InvalidArgumentException(sprintf(
+ 'Cache key must be string, "%s" given',
+ gettype($key)
+ ));
+ $this->handleException($e, __FUNCTION__);
+ }
+ if (!isset($key[0])) {
+ $e = new InvalidArgumentException('Cache key cannot be an empty string');
+ $this->handleException($e, __FUNCTION__);
+ }
+ if (preg_match('|[\{\}\(\)/\\\@\:]|', $key)) {
+ $e = new InvalidArgumentException(sprintf(
+ 'Invalid key: "%s". The key contains one or more characters reserved for future extension: {}()/\@:',
+ $key
+ ));
+ $this->handleException($e, __FUNCTION__);
+ }
+ }
+
+ /**
+ * @param LoggerInterface $logger
+ */
+ public function setLogger(LoggerInterface $logger): void
+ {
+ $this->logger = $logger;
+ }
+
+ /**
+ * Logs with an arbitrary level if the logger exists.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ */
+ protected function log($level, $message, array $context = [])
+ {
+ if ($this->logger !== null) {
+ $this->logger->log($level, $message, $context);
+ }
+ }
+
+ /**
+ * Log exception and rethrow it.
+ *
+ * @param \Exception $e
+ * @param string $function
+ *
+ * @throws CachePoolException
+ */
+ private function handleException(\Exception $e, $function)
+ {
+ $level = 'alert';
+ if ($e instanceof InvalidArgumentException) {
+ $level = 'warning';
+ }
+
+ $this->log($level, $e->getMessage(), ['exception' => $e]);
+ if (!$e instanceof CacheException) {
+ $e = new CachePoolException(sprintf('Exception thrown when executing "%s". ', $function), 0, $e);
+ }
+
+ throw $e;
+ }
+
+ /**
+ * @param array $tags
+ *
+ * @return bool
+ */
+ public function invalidateTags(array $tags)
+ {
+ $itemIds = [];
+ foreach ($tags as $tag) {
+ $itemIds = array_merge($itemIds, $this->getList($this->getTagKey($tag)));
+ }
+
+ // Remove all items with the tag
+ $success = $this->deleteItems($itemIds);
+
+ if ($success) {
+ // Remove the tag list
+ foreach ($tags as $tag) {
+ $this->removeList($this->getTagKey($tag));
+ $l = $this->getList($this->getTagKey($tag));
+ }
+ }
+
+ return $success;
+ }
+
+ public function invalidateTag($tag)
+ {
+ return $this->invalidateTags([$tag]);
+ }
+
+ /**
+ * @param PhpCacheItem $item
+ */
+ protected function saveTags(PhpCacheItem $item)
+ {
+ $tags = $item->getTags();
+ foreach ($tags as $tag) {
+ $this->appendListItem($this->getTagKey($tag), $item->getKey());
+ }
+ }
+
+ /**
+ * Removes the key form all tag lists. When an item with tags is removed
+ * we MUST remove the tags. If we fail to remove the tags a new item with
+ * the same key will automatically get the previous tags.
+ *
+ * @param string $key
+ *
+ * @return $this
+ */
+ protected function preRemoveItem($key)
+ {
+ $item = $this->getItem($key);
+ $this->removeTagEntries($item);
+
+ return $this;
+ }
+
+ /**
+ * @param PhpCacheItem $item
+ */
+ private function removeTagEntries(PhpCacheItem $item)
+ {
+ $tags = $item->getPreviousTags();
+ foreach ($tags as $tag) {
+ $this->removeListItem($this->getTagKey($tag), $item->getKey());
+ }
+ }
+
+ /**
+ * @param string $tag
+ *
+ * @return string
+ */
+ protected function getTagKey($tag)
+ {
+ return 'tag'.self::SEPARATOR_TAG.$tag;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($key, $default = null)
+ {
+ $item = $this->getItem($key);
+ if (!$item->isHit()) {
+ return $default;
+ }
+
+ return $item->get();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ $item = $this->getItem($key);
+ $item->set($value);
+ $item->expiresAfter($ttl);
+
+ return $this->save($item);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete($key)
+ {
+ return $this->deleteItem($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ if (!is_array($keys)) {
+ if (!$keys instanceof \Traversable) {
+ throw new InvalidArgumentException('$keys is neither an array nor Traversable');
+ }
+
+ // Since we need to throw an exception if *any* key is invalid, it doesn't
+ // make sense to wrap iterators or something like that.
+ $keys = iterator_to_array($keys, false);
+ }
+
+ $items = $this->getItems($keys);
+
+ return $this->generateValues($default, $items);
+ }
+
+ /**
+ * @param $default
+ * @param $items
+ *
+ * @return \Generator
+ */
+ private function generateValues($default, $items)
+ {
+ foreach ($items as $key => $item) {
+ /** @type $item CacheItemInterface */
+ if (!$item->isHit()) {
+ yield $key => $default;
+ } else {
+ yield $key => $item->get();
+ }
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ if (!is_array($values)) {
+ if (!$values instanceof \Traversable) {
+ throw new InvalidArgumentException('$values is neither an array nor Traversable');
+ }
+ }
+
+ $keys = [];
+ $arrayValues = [];
+ foreach ($values as $key => $value) {
+ if (is_int($key)) {
+ $key = (string) $key;
+ }
+ $this->validateKey($key);
+ $keys[] = $key;
+ $arrayValues[$key] = $value;
+ }
+
+ $items = $this->getItems($keys);
+ $itemSuccess = true;
+ foreach ($items as $key => $item) {
+ $item->set($arrayValues[$key]);
+
+ try {
+ $item->expiresAfter($ttl);
+ } catch (InvalidArgumentException $e) {
+ throw new InvalidArgumentException($e->getMessage(), $e->getCode(), $e);
+ }
+
+ $itemSuccess = $itemSuccess && $this->saveDeferred($item);
+ }
+
+ return $itemSuccess && $this->commit();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteMultiple($keys)
+ {
+ if (!is_array($keys)) {
+ if (!$keys instanceof \Traversable) {
+ throw new InvalidArgumentException('$keys is neither an array nor Traversable');
+ }
+
+ // Since we need to throw an exception if *any* key is invalid, it doesn't
+ // make sense to wrap iterators or something like that.
+ $keys = iterator_to_array($keys, false);
+ }
+
+ return $this->deleteItems($keys);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function has($key)
+ {
+ return $this->hasItem($key);
+ }
+}
diff --git a/vendor/cache/adapter-common/CacheItem.php b/vendor/cache/adapter-common/CacheItem.php
new file mode 100644
index 0000000..47e68c3
--- /dev/null
+++ b/vendor/cache/adapter-common/CacheItem.php
@@ -0,0 +1,269 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common;
+
+use Cache\Adapter\Common\Exception\InvalidArgumentException;
+use Cache\TagInterop\TaggableCacheItemInterface;
+
+/**
+ * @author Aaron Scherer
+ * @author Tobias Nyholm
+ */
+class CacheItem implements PhpCacheItem
+{
+ /**
+ * @type array
+ */
+ private $prevTags = [];
+
+ /**
+ * @type array
+ */
+ private $tags = [];
+
+ /**
+ * @type \Closure
+ */
+ private $callable;
+
+ /**
+ * @type string
+ */
+ private $key;
+
+ /**
+ * @type mixed
+ */
+ private $value;
+
+ /**
+ * The expiration timestamp is the source of truth. This is the UTC timestamp
+ * when the cache item expire. A value of zero means it never expires. A nullvalue
+ * means that no expiration is set.
+ *
+ * @type int|null
+ */
+ private $expirationTimestamp = null;
+
+ /**
+ * @type bool
+ */
+ private $hasValue = false;
+
+ /**
+ * @param string $key
+ * @param \Closure|bool $callable or boolean hasValue
+ */
+ public function __construct($key, $callable = null, $value = null)
+ {
+ $this->key = $key;
+
+ if ($callable === true) {
+ $this->hasValue = true;
+ $this->value = $value;
+ } elseif ($callable !== false) {
+ // This must be a callable or null
+ $this->callable = $callable;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($value)
+ {
+ $this->value = $value;
+ $this->hasValue = true;
+ $this->callable = null;
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get()
+ {
+ if (!$this->isHit()) {
+ return;
+ }
+
+ return $this->value;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isHit()
+ {
+ $this->initialize();
+
+ if (!$this->hasValue) {
+ return false;
+ }
+
+ if ($this->expirationTimestamp !== null) {
+ return $this->expirationTimestamp > time();
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getExpirationTimestamp()
+ {
+ return $this->expirationTimestamp;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function expiresAt($expiration)
+ {
+ if ($expiration instanceof \DateTimeInterface) {
+ $this->expirationTimestamp = $expiration->getTimestamp();
+ } elseif (is_int($expiration) || null === $expiration) {
+ $this->expirationTimestamp = $expiration;
+ } else {
+ throw new InvalidArgumentException('Cache item ttl/expiresAt must be of type integer or \DateTimeInterface.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function expiresAfter($time)
+ {
+ if ($time === null) {
+ $this->expirationTimestamp = null;
+ } elseif ($time instanceof \DateInterval) {
+ $date = new \DateTime();
+ $date->add($time);
+ $this->expirationTimestamp = $date->getTimestamp();
+ } elseif (is_int($time)) {
+ $this->expirationTimestamp = time() + $time;
+ } else {
+ throw new InvalidArgumentException('Cache item ttl/expiresAfter must be of type integer or \DateInterval.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getPreviousTags()
+ {
+ $this->initialize();
+
+ return $this->prevTags;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTags()
+ {
+ return $this->tags;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function setTags(array $tags)
+ {
+ $this->tags = [];
+ $this->tag($tags);
+
+ return $this;
+ }
+
+ /**
+ * Adds a tag to a cache item.
+ *
+ * @param string|string[] $tags A tag or array of tags
+ *
+ * @throws InvalidArgumentException When $tag is not valid.
+ *
+ * @return TaggableCacheItemInterface
+ */
+ private function tag($tags)
+ {
+ $this->initialize();
+
+ if (!is_array($tags)) {
+ $tags = [$tags];
+ }
+ foreach ($tags as $tag) {
+ if (!is_string($tag)) {
+ throw new InvalidArgumentException(sprintf('Cache tag must be string, "%s" given', is_object($tag) ? get_class($tag) : gettype($tag)));
+ }
+ if (isset($this->tags[$tag])) {
+ continue;
+ }
+ if (!isset($tag[0])) {
+ throw new InvalidArgumentException('Cache tag length must be greater than zero');
+ }
+ if (isset($tag[strcspn($tag, '{}()/\@:')])) {
+ throw new InvalidArgumentException(sprintf('Cache tag "%s" contains reserved characters {}()/\@:', $tag));
+ }
+ $this->tags[$tag] = $tag;
+ }
+
+ return $this;
+ }
+
+ /**
+ * If callable is not null, execute it an populate this object with values.
+ */
+ private function initialize()
+ {
+ if ($this->callable !== null) {
+ // $func will be $adapter->fetchObjectFromCache();
+ $func = $this->callable;
+ $result = $func();
+ $this->hasValue = $result[0];
+ $this->value = $result[1];
+ $this->prevTags = isset($result[2]) ? $result[2] : [];
+ $this->expirationTimestamp = null;
+
+ if (isset($result[3]) && is_int($result[3])) {
+ $this->expirationTimestamp = $result[3];
+ }
+
+ $this->callable = null;
+ }
+ }
+
+ /**
+ * @internal This function should never be used and considered private.
+ *
+ * Move tags from $tags to $prevTags
+ */
+ public function moveTagsToPrevious()
+ {
+ $this->prevTags = $this->tags;
+ $this->tags = [];
+ }
+}
diff --git a/vendor/cache/adapter-common/Changelog.md b/vendor/cache/adapter-common/Changelog.md
new file mode 100644
index 0000000..5a82188
--- /dev/null
+++ b/vendor/cache/adapter-common/Changelog.md
@@ -0,0 +1,73 @@
+# Change Log
+
+The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+
+## 1.3.0
+
+* Support for PHP 8.1
+* Drop support for PHP < 7.4
+* Allow psr/cache: ^1.0 || ^2.0
+
+## 1.2.0
+
+### Added
+
+* Support for PHP 8
+
+## 1.1.0
+
+### Added
+
+- Support for storing binary data
+
+### Fixed
+
+- Issue with one character variables
+
+### Changed
+
+- Tests are now extending `PHPUnit\Framework\TestCase`
+
+## 1.0.0
+
+* No changes since 0.4.0.
+
+## 0.4.0
+
+### Added
+
+* `AbstractCachePool` has 4 new abstract methods: `getList`, `removeList`, `appendListItem` and `removeListItem`.
+* `AbstractCachePool::invalidateTags` and `AbstractCachePool::invalidateTags`
+* Added interfaces for our items and pools `PhpCachePool` and `PhpCacheItem`
+* Trait to help adapters to support tags. `TagSupportWithArray`.
+
+### Changed
+
+* First parameter to `AbstractCachePool::storeItemInCache` must be a `PhpCacheItem`.
+* Return value from `AbstractCachePool::fetchObjectFromCache` must be a an array with 4 values. Added expiration timestamp.
+* `HasExpirationDateInterface` is replaced by `HasExpirationTimestampInterface`
+* We do not work with `\DateTime` internally anymore. We work with timestamps.
+
+## 0.3.3
+
+### Fixed
+
+* Bugfix when you fetch data from the cache storage that was saved as "non-tagging item" but fetch as a tagging item.
+
+## 0.3.2
+
+### Added
+
+* Cache pools do implement `LoggerAwareInterface`
+
+## 0.3.0
+
+### Changed
+
+* The `AbstractCachePool` does not longer implement `TaggablePoolInterface`. However, the `CacheItem` does still implement `TaggableItemInterface`.
+* `CacheItem::getKeyFromTaggedKey` has been removed
+* The `CacheItem`'s second parameter is a callable that must return an array with 3 elements; [`hasValue`, `value`, `tags`].
+
+## 0.2.0
+
+* No changelog before this version
diff --git a/vendor/cache/adapter-common/Exception/CacheException.php b/vendor/cache/adapter-common/Exception/CacheException.php
new file mode 100644
index 0000000..54fbb11
--- /dev/null
+++ b/vendor/cache/adapter-common/Exception/CacheException.php
@@ -0,0 +1,23 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common\Exception;
+
+use Psr\Cache\CacheException as CacheExceptionInterface;
+
+/**
+ * A base exception. All exceptions in this organization will extend this exception.
+ *
+ * @author Tobias Nyholm
+ */
+abstract class CacheException extends \RuntimeException implements CacheExceptionInterface
+{
+}
diff --git a/vendor/cache/adapter-common/Exception/CachePoolException.php b/vendor/cache/adapter-common/Exception/CachePoolException.php
new file mode 100644
index 0000000..c0b7e59
--- /dev/null
+++ b/vendor/cache/adapter-common/Exception/CachePoolException.php
@@ -0,0 +1,21 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common\Exception;
+
+/**
+ * If an exception is caused by a pool or by the cache storage.
+ *
+ * @author Tobias Nyholm
+ */
+class CachePoolException extends CacheException
+{
+}
diff --git a/vendor/cache/adapter-common/Exception/InvalidArgumentException.php b/vendor/cache/adapter-common/Exception/InvalidArgumentException.php
new file mode 100644
index 0000000..e3cc8f4
--- /dev/null
+++ b/vendor/cache/adapter-common/Exception/InvalidArgumentException.php
@@ -0,0 +1,19 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common\Exception;
+
+use Psr\Cache\InvalidArgumentException as CacheInvalidArgumentException;
+use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentException;
+
+class InvalidArgumentException extends CacheException implements CacheInvalidArgumentException, SimpleCacheInvalidArgumentException
+{
+}
diff --git a/vendor/cache/adapter-common/HasExpirationTimestampInterface.php b/vendor/cache/adapter-common/HasExpirationTimestampInterface.php
new file mode 100644
index 0000000..22f0adf
--- /dev/null
+++ b/vendor/cache/adapter-common/HasExpirationTimestampInterface.php
@@ -0,0 +1,26 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common;
+
+/**
+ * @author Aaron Scherer
+ * @author Tobias Nyholm
+ */
+interface HasExpirationTimestampInterface
+{
+ /**
+ * The timestamp when the object expires.
+ *
+ * @return int|null
+ */
+ public function getExpirationTimestamp();
+}
diff --git a/vendor/cache/adapter-common/JsonBinaryArmoring.php b/vendor/cache/adapter-common/JsonBinaryArmoring.php
new file mode 100644
index 0000000..7e3d91d
--- /dev/null
+++ b/vendor/cache/adapter-common/JsonBinaryArmoring.php
@@ -0,0 +1,68 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common;
+
+/**
+ * This trait provides common routines for safely encoding binary and non-UTF8 data in
+ * JSON. This is needed for components that use JSON natively (currently, the MongoDB
+ * adapter and EncryptedCachePool).
+ *
+ * @author Stephen Clouse
+ */
+trait JsonBinaryArmoring
+{
+ private static $ESCAPE_JSON_CHARACTERS = [
+ "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
+ "\x08", "\x09", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F",
+ "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
+ "\x18", "\x19", "\x1A", "\x1B", "\x1C", "\x1D", "\x1E", "\x1F",
+ ];
+
+ private static $ENCODED_JSON_CHARACTERS = [
+ '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007',
+ '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F',
+ '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017',
+ '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F',
+ ];
+
+ /**
+ * Armor a value going into a JSON document.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function jsonArmor($value)
+ {
+ return str_replace(
+ static::$ESCAPE_JSON_CHARACTERS,
+ static::$ENCODED_JSON_CHARACTERS,
+ utf8_encode($value)
+ );
+ }
+
+ /**
+ * De-armor a value from a JSON document.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ protected static function jsonDeArmor($value)
+ {
+ return utf8_decode(str_replace(
+ static::$ENCODED_JSON_CHARACTERS,
+ static::$ESCAPE_JSON_CHARACTERS,
+ $value
+ ));
+ }
+}
diff --git a/vendor/cache/adapter-common/LICENSE b/vendor/cache/adapter-common/LICENSE
new file mode 100644
index 0000000..82f8fee
--- /dev/null
+++ b/vendor/cache/adapter-common/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Aaron Scherer, Tobias Nyholm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/cache/adapter-common/PhpCacheItem.php b/vendor/cache/adapter-common/PhpCacheItem.php
new file mode 100644
index 0000000..8d6ed5e
--- /dev/null
+++ b/vendor/cache/adapter-common/PhpCacheItem.php
@@ -0,0 +1,32 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common;
+
+use Cache\TagInterop\TaggableCacheItemInterface;
+
+/**
+ * @author Tobias Nyholm
+ */
+interface PhpCacheItem extends HasExpirationTimestampInterface, TaggableCacheItemInterface
+{
+ /**
+ * Get the current tags. These are not the same tags as getPrevious tags. This
+ * is the tags that has been added to the item after the item was fetched from
+ * the cache storage.
+ *
+ * WARNING: This is generally not the function you want to use. Please see
+ * `getPreviousTags`.
+ *
+ * @return array
+ */
+ public function getTags();
+}
diff --git a/vendor/cache/adapter-common/PhpCachePool.php b/vendor/cache/adapter-common/PhpCachePool.php
new file mode 100644
index 0000000..5ccfb67
--- /dev/null
+++ b/vendor/cache/adapter-common/PhpCachePool.php
@@ -0,0 +1,34 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common;
+
+use Cache\TagInterop\TaggableCacheItemPoolInterface;
+
+/**
+ * @author Tobias Nyholm
+ */
+interface PhpCachePool extends TaggableCacheItemPoolInterface
+{
+ /**
+ * {@inheritdoc}
+ *
+ * @return PhpCacheItem
+ */
+ public function getItem($key);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return array|\Traversable|PhpCacheItem[]
+ */
+ public function getItems(array $keys = []);
+}
diff --git a/vendor/cache/adapter-common/README.md b/vendor/cache/adapter-common/README.md
new file mode 100644
index 0000000..3ce3480
--- /dev/null
+++ b/vendor/cache/adapter-common/README.md
@@ -0,0 +1,15 @@
+# Common PSR-6 Cache pool
+[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+[![Latest Stable Version](https://poser.pugx.org/cache/adapter-common/v/stable)](https://packagist.org/packages/cache/adapter-common)
+[![codecov.io](https://codecov.io/github/php-cache/adapter-common/coverage.svg?branch=master)](https://codecov.io/github/php-cache/adapter-common?branch=master)
+[![Total Downloads](https://poser.pugx.org/cache/adapter-common/downloads)](https://packagist.org/packages/cache/adapter-common)
+[![Monthly Downloads](https://poser.pugx.org/cache/adapter-common/d/monthly.png)](https://packagist.org/packages/cache/adapter-common)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
+
+This repository contains shared classes and interfaces used by the PHP Cache organisation. To read about
+features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com).
+
+### Contribute
+
+Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or
+report any issues you find on the [issue tracker](http://issues.php-cache.com).
diff --git a/vendor/cache/adapter-common/TagSupportWithArray.php b/vendor/cache/adapter-common/TagSupportWithArray.php
new file mode 100644
index 0000000..81859d2
--- /dev/null
+++ b/vendor/cache/adapter-common/TagSupportWithArray.php
@@ -0,0 +1,88 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Common;
+
+/**
+ * This trait could be used by adapters that do not have a native support for lists.
+ *
+ * @author Tobias Nyholm
+ */
+trait TagSupportWithArray
+{
+ /**
+ * Get a value from the storage.
+ *
+ * @param string $name
+ *
+ * @return mixed
+ */
+ abstract public function getDirectValue($name);
+
+ /**
+ * Set a value to the storage.
+ *
+ * @param string $name
+ * @param mixed $value
+ */
+ abstract public function setDirectValue($name, $value);
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function appendListItem($name, $value)
+ {
+ $data = $this->getDirectValue($name);
+ if (!is_array($data)) {
+ $data = [];
+ }
+ $data[] = $value;
+ $this->setDirectValue($name, $data);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getList($name)
+ {
+ $data = $this->getDirectValue($name);
+ if (!is_array($data)) {
+ $data = [];
+ }
+
+ return $data;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function removeList($name)
+ {
+ $this->setDirectValue($name, []);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function removeListItem($name, $key)
+ {
+ $data = $this->getList($name);
+ foreach ($data as $i => $value) {
+ if ($key === $value) {
+ unset($data[$i]);
+ }
+ }
+
+ return $this->setDirectValue($name, $data);
+ }
+}
diff --git a/vendor/cache/adapter-common/composer.json b/vendor/cache/adapter-common/composer.json
new file mode 100644
index 0000000..c773462
--- /dev/null
+++ b/vendor/cache/adapter-common/composer.json
@@ -0,0 +1,55 @@
+{
+ "name": "cache/adapter-common",
+ "description": "Common classes for PSR-6 adapters",
+ "license": "MIT",
+ "type": "library",
+ "keywords": [
+ "cache",
+ "psr-6",
+ "tag"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Scherer",
+ "email": "aequasi@gmail.com",
+ "homepage": "https://github.com/aequasi"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ }
+ ],
+ "homepage": "http://www.php-cache.com/en/latest/",
+ "require": {
+ "php": ">=7.4",
+ "cache/tag-interop": "^1.0",
+ "psr/cache": "^1.0 || ^2.0",
+ "psr/log": "^1.0 || ^2.0 || ^3.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "^0.17",
+ "phpunit/phpunit": "^7.5.20 || ^9.5.10"
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "autoload": {
+ "psr-4": {
+ "Cache\\Adapter\\Common\\": ""
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Cache\\Adapter\\Common\\Tests\\": "Tests/"
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ }
+}
diff --git a/vendor/cache/filesystem-adapter/Changelog.md b/vendor/cache/filesystem-adapter/Changelog.md
new file mode 100644
index 0000000..bd1f902
--- /dev/null
+++ b/vendor/cache/filesystem-adapter/Changelog.md
@@ -0,0 +1,64 @@
+# Change Log
+
+The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+
+## UNRELEASED
+
+## 1.2.0
+
+* Support for PHP 8.1
+* Drop support for PHP < 7.4
+* Allow psr/cache: ^1.0 || ^2.0
+
+## 1.1.0
+
+### Added
+
+* Support for PHP 8
+
+### Changed
+
+* Use `League\Flysystem\FilesystemInterface` instead of concrete `League\Flysystem\Filesystem` class
+
+## 1.0.0
+
+* No changes since 0.4.0
+
+## 0.4.0
+
+### Added
+
+* Support for the new `TaggableCacheItemPoolInterface`.
+* Support for PSR-16 SimpleCache
+
+### Changed
+
+* The behavior of `CacheItem::getTags()` has changed. It will not return the tags stored in the cache storage.
+
+### Removed
+
+* `CacheItem::getExpirationDate()`. Use `CacheItem::getExpirationTimestamp()`
+* `CacheItem::getTags()`. Use `CacheItem::getPreviousTags()`
+* `CacheItem::addTag()`. Use `CacheItem::setTags()`
+
+## 0.3.3
+
+### Fixed
+
+* Race condition in `fetchObjectFromCache`.
+
+## 0.3.2
+
+### Changed
+
+* Using `Filesystem::update` instead of `Filesystem::delete` and `Filesystem::write`.
+
+## 0.3.1
+
+### Added
+
+* Add ability to change cache path in FilesystemCachePool
+
+## 0.3.0
+
+* No changelog before this version
diff --git a/vendor/cache/filesystem-adapter/FilesystemCachePool.php b/vendor/cache/filesystem-adapter/FilesystemCachePool.php
new file mode 100644
index 0000000..065519d
--- /dev/null
+++ b/vendor/cache/filesystem-adapter/FilesystemCachePool.php
@@ -0,0 +1,213 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\Adapter\Filesystem;
+
+use Cache\Adapter\Common\AbstractCachePool;
+use Cache\Adapter\Common\Exception\InvalidArgumentException;
+use Cache\Adapter\Common\PhpCacheItem;
+use League\Flysystem\FileExistsException;
+use League\Flysystem\FileNotFoundException;
+use League\Flysystem\FilesystemInterface;
+
+/**
+ * @author Tobias Nyholm
+ */
+class FilesystemCachePool extends AbstractCachePool
+{
+ /**
+ * @type FilesystemInterface
+ */
+ private $filesystem;
+
+ /**
+ * The folder should not begin nor end with a slash. Example: path/to/cache.
+ *
+ * @type string
+ */
+ private $folder;
+
+ /**
+ * @param FilesystemInterface $filesystem
+ * @param string $folder
+ */
+ public function __construct(FilesystemInterface $filesystem, $folder = 'cache')
+ {
+ $this->folder = $folder;
+
+ $this->filesystem = $filesystem;
+ $this->filesystem->createDir($this->folder);
+ }
+
+ /**
+ * @param string $folder
+ */
+ public function setFolder($folder)
+ {
+ $this->folder = $folder;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function fetchObjectFromCache($key)
+ {
+ $empty = [false, null, [], null];
+ $file = $this->getFilePath($key);
+
+ try {
+ $data = @unserialize($this->filesystem->read($file));
+ if ($data === false) {
+ return $empty;
+ }
+ } catch (FileNotFoundException $e) {
+ return $empty;
+ }
+
+ // Determine expirationTimestamp from data, remove items if expired
+ $expirationTimestamp = $data[2] ?: null;
+ if ($expirationTimestamp !== null && time() > $expirationTimestamp) {
+ foreach ($data[1] as $tag) {
+ $this->removeListItem($this->getTagKey($tag), $key);
+ }
+ $this->forceClear($key);
+
+ return $empty;
+ }
+
+ return [true, $data[0], $data[1], $expirationTimestamp];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function clearAllObjectsFromCache()
+ {
+ $this->filesystem->deleteDir($this->folder);
+ $this->filesystem->createDir($this->folder);
+
+ return true;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function clearOneObjectFromCache($key)
+ {
+ return $this->forceClear($key);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function storeItemInCache(PhpCacheItem $item, $ttl)
+ {
+ $data = serialize(
+ [
+ $item->get(),
+ $item->getTags(),
+ $item->getExpirationTimestamp(),
+ ]
+ );
+
+ $file = $this->getFilePath($item->getKey());
+ if ($this->filesystem->has($file)) {
+ // Update file if it exists
+ return $this->filesystem->update($file, $data);
+ }
+
+ try {
+ return $this->filesystem->write($file, $data);
+ } catch (FileExistsException $e) {
+ // To handle issues when/if race conditions occurs, we try to update here.
+ return $this->filesystem->update($file, $data);
+ }
+ }
+
+ /**
+ * @param string $key
+ *
+ * @throws InvalidArgumentException
+ *
+ * @return string
+ */
+ private function getFilePath($key)
+ {
+ if (!preg_match('|^[a-zA-Z0-9_\.! ]+$|', $key)) {
+ throw new InvalidArgumentException(sprintf('Invalid key "%s". Valid filenames must match [a-zA-Z0-9_\.! ].', $key));
+ }
+
+ return sprintf('%s/%s', $this->folder, $key);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getList($name)
+ {
+ $file = $this->getFilePath($name);
+
+ if (!$this->filesystem->has($file)) {
+ $this->filesystem->write($file, serialize([]));
+ }
+
+ return unserialize($this->filesystem->read($file));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function removeList($name)
+ {
+ $file = $this->getFilePath($name);
+ $this->filesystem->delete($file);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function appendListItem($name, $key)
+ {
+ $list = $this->getList($name);
+ $list[] = $key;
+
+ return $this->filesystem->update($this->getFilePath($name), serialize($list));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function removeListItem($name, $key)
+ {
+ $list = $this->getList($name);
+ foreach ($list as $i => $item) {
+ if ($item === $key) {
+ unset($list[$i]);
+ }
+ }
+
+ return $this->filesystem->update($this->getFilePath($name), serialize($list));
+ }
+
+ /**
+ * @param $key
+ *
+ * @return bool
+ */
+ private function forceClear($key)
+ {
+ try {
+ return $this->filesystem->delete($this->getFilePath($key));
+ } catch (FileNotFoundException $e) {
+ return true;
+ }
+ }
+}
diff --git a/vendor/cache/filesystem-adapter/LICENSE b/vendor/cache/filesystem-adapter/LICENSE
new file mode 100644
index 0000000..82f8fee
--- /dev/null
+++ b/vendor/cache/filesystem-adapter/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Aaron Scherer, Tobias Nyholm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/cache/filesystem-adapter/README.md b/vendor/cache/filesystem-adapter/README.md
new file mode 100644
index 0000000..90b4e45
--- /dev/null
+++ b/vendor/cache/filesystem-adapter/README.md
@@ -0,0 +1,45 @@
+# Filesystem PSR-6 Cache pool
+[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+[![Latest Stable Version](https://poser.pugx.org/cache/filesystem-adapter/v/stable)](https://packagist.org/packages/cache/filesystem-adapter)
+[![codecov.io](https://codecov.io/github/php-cache/filesystem-adapter/coverage.svg?branch=master)](https://codecov.io/github/php-cache/filesystem-adapter?branch=master)
+[![Total Downloads](https://poser.pugx.org/cache/filesystem-adapter/downloads)](https://packagist.org/packages/cache/filesystem-adapter)
+[![Monthly Downloads](https://poser.pugx.org/cache/filesystem-adapter/d/monthly.png)](https://packagist.org/packages/cache/filesystem-adapter)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
+
+This is a PSR-6 cache implementation using Filesystem. It is a part of the PHP Cache organisation. To read about
+features like tagging and hierarchy support please read the shared documentation at [www.php-cache.com](http://www.php-cache.com).
+
+This implementation is using the excellent [Flysystem](http://flysystem.thephpleague.com/).
+
+### Install
+
+```bash
+composer require cache/filesystem-adapter
+```
+
+### Use
+
+To create an instance of `FilesystemCachePool` you need to configure a `Filesystem` and its adapter.
+
+```php
+use League\Flysystem\Adapter\Local;
+use League\Flysystem\Filesystem;
+use Cache\Adapter\Filesystem\FilesystemCachePool;
+
+$filesystemAdapter = new Local(__DIR__.'/');
+$filesystem = new Filesystem($filesystemAdapter);
+
+$pool = new FilesystemCachePool($filesystem);
+```
+
+You can change the folder the cache pool will write to through the `setFolder` setter:
+
+```php
+$pool = new FilesystemCachePool($filesystem);
+$pool->setFolder('path/to/cache');
+```
+
+### Contribute
+
+Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or
+report any issues you find on the [issue tracker](http://issues.php-cache.com).
diff --git a/vendor/cache/filesystem-adapter/composer.json b/vendor/cache/filesystem-adapter/composer.json
new file mode 100644
index 0000000..d2f794d
--- /dev/null
+++ b/vendor/cache/filesystem-adapter/composer.json
@@ -0,0 +1,55 @@
+{
+ "name": "cache/filesystem-adapter",
+ "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags",
+ "license": "MIT",
+ "type": "library",
+ "keywords": [
+ "cache",
+ "psr-6",
+ "filesystem",
+ "tag"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Scherer",
+ "email": "aequasi@gmail.com",
+ "homepage": "https://github.com/aequasi"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ }
+ ],
+ "homepage": "http://www.php-cache.com/en/latest/",
+ "require": {
+ "php": ">=7.4",
+ "cache/adapter-common": "^1.0",
+ "league/flysystem": "^1.0",
+ "psr/cache": "^1.0 || ^2.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "^0.17",
+ "phpunit/phpunit": "^7.5.20 || ^9.5.10"
+ },
+ "provide": {
+ "psr/cache-implementation": "^1.0",
+ "psr/simple-cache-implementation": "^1.0"
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "autoload": {
+ "psr-4": {
+ "Cache\\Adapter\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ }
+}
diff --git a/vendor/cache/tag-interop/.github/PULL_REQUEST_TEMPLATE.md b/vendor/cache/tag-interop/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..4a339b4
--- /dev/null
+++ b/vendor/cache/tag-interop/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,5 @@
+This is a READ ONLY repository.
+
+Please make your pull request to https://github.com/php-cache/cache
+
+Thank you for contributing.
diff --git a/vendor/cache/tag-interop/.gitignore b/vendor/cache/tag-interop/.gitignore
new file mode 100644
index 0000000..987e2a2
--- /dev/null
+++ b/vendor/cache/tag-interop/.gitignore
@@ -0,0 +1,2 @@
+composer.lock
+vendor
diff --git a/vendor/cache/tag-interop/.travis.yml b/vendor/cache/tag-interop/.travis.yml
new file mode 100644
index 0000000..45a1672
--- /dev/null
+++ b/vendor/cache/tag-interop/.travis.yml
@@ -0,0 +1,22 @@
+language: php
+sudo: false
+
+matrix:
+ include:
+ - php: 7.1
+
+cache:
+ directories:
+ - "$HOME/.composer/cache"
+
+install:
+ - composer update --prefer-dist --prefer-stable
+
+script:
+ - ./vendor/bin/phpunit --coverage-clover=coverage.xml
+
+after_success:
+ - pip install --user codecov && codecov
+
+notifications:
+ email: false
diff --git a/vendor/cache/tag-interop/Changelog.md b/vendor/cache/tag-interop/Changelog.md
new file mode 100644
index 0000000..974da3e
--- /dev/null
+++ b/vendor/cache/tag-interop/Changelog.md
@@ -0,0 +1,18 @@
+# Change Log
+
+The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
+
+## 1.1.0
+
+* Support PHP 8.1
+* Support for psr/cache v2
+
+## 1.0.1
+
+* Support PHP 8
+
+## 1.0.0
+
+* First release
+
+
diff --git a/vendor/cache/tag-interop/LICENSE b/vendor/cache/tag-interop/LICENSE
new file mode 100644
index 0000000..82f8fee
--- /dev/null
+++ b/vendor/cache/tag-interop/LICENSE
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Aaron Scherer, Tobias Nyholm
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
diff --git a/vendor/cache/tag-interop/README.md b/vendor/cache/tag-interop/README.md
new file mode 100644
index 0000000..28511c9
--- /dev/null
+++ b/vendor/cache/tag-interop/README.md
@@ -0,0 +1,25 @@
+# Tag support for PSR-6 Cache
+[![Gitter](https://badges.gitter.im/php-cache/cache.svg)](https://gitter.im/php-cache/cache?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
+[![Latest Stable Version](https://poser.pugx.org/cache/tag-interop/v/stable)](https://packagist.org/packages/cache/tag-interop)
+[![Total Downloads](https://poser.pugx.org/cache/tag-interop/downloads)](https://packagist.org/packages/cache/tag-interop)
+[![Monthly Downloads](https://poser.pugx.org/cache/tag-interop/d/monthly.png)](https://packagist.org/packages/cache/tag-interop)
+[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
+
+This repository holds two interfaces for tagging. These interfaces will make their
+way into PHP Fig. Representatives from Symfony, PHP-cache and Drupal has worked
+together to agree on these interfaces.
+
+### Install
+
+```bash
+composer require cache/tag-interop
+```
+
+### Use
+
+Read the [documentation on usage](http://www.php-cache.com/).
+
+### Contribute
+
+Contributions are very welcome! Send a pull request to the [main repository](https://github.com/php-cache/cache) or
+report any issues you find on the [issue tracker](http://issues.php-cache.com).
diff --git a/vendor/cache/tag-interop/TaggableCacheItemInterface.php b/vendor/cache/tag-interop/TaggableCacheItemInterface.php
new file mode 100644
index 0000000..5823b0b
--- /dev/null
+++ b/vendor/cache/tag-interop/TaggableCacheItemInterface.php
@@ -0,0 +1,43 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\TagInterop;
+
+use Psr\Cache\CacheItemInterface;
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * An item that supports tags. This interface is a soon-to-be-PSR.
+ *
+ * @author Tobias Nyholm
+ * @author Nicolas Grekas
+ */
+interface TaggableCacheItemInterface extends CacheItemInterface
+{
+ /**
+ * Get all existing tags. These are the tags the item has when the item is
+ * returned from the pool.
+ *
+ * @return array
+ */
+ public function getPreviousTags();
+
+ /**
+ * Overwrite all tags with a new set of tags.
+ *
+ * @param string[] $tags An array of tags
+ *
+ * @throws InvalidArgumentException When a tag is not valid.
+ *
+ * @return TaggableCacheItemInterface
+ */
+ public function setTags(array $tags);
+}
diff --git a/vendor/cache/tag-interop/TaggableCacheItemPoolInterface.php b/vendor/cache/tag-interop/TaggableCacheItemPoolInterface.php
new file mode 100644
index 0000000..055bf4b
--- /dev/null
+++ b/vendor/cache/tag-interop/TaggableCacheItemPoolInterface.php
@@ -0,0 +1,60 @@
+, Tobias Nyholm
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+namespace Cache\TagInterop;
+
+use Psr\Cache\CacheItemPoolInterface;
+use Psr\Cache\InvalidArgumentException;
+
+/**
+ * Interface for invalidating cached items using tags. This interface is a soon-to-be-PSR.
+ *
+ * @author Tobias Nyholm
+ * @author Nicolas Grekas
+ */
+interface TaggableCacheItemPoolInterface extends CacheItemPoolInterface
+{
+ /**
+ * Invalidates cached items using a tag.
+ *
+ * @param string $tag The tag to invalidate
+ *
+ * @throws InvalidArgumentException When $tags is not valid
+ *
+ * @return bool True on success
+ */
+ public function invalidateTag($tag);
+
+ /**
+ * Invalidates cached items using tags.
+ *
+ * @param string[] $tags An array of tags to invalidate
+ *
+ * @throws InvalidArgumentException When $tags is not valid
+ *
+ * @return bool True on success
+ */
+ public function invalidateTags(array $tags);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return TaggableCacheItemInterface
+ */
+ public function getItem($key);
+
+ /**
+ * {@inheritdoc}
+ *
+ * @return array|\Traversable|TaggableCacheItemInterface[]
+ */
+ public function getItems(array $keys = []);
+}
diff --git a/vendor/cache/tag-interop/composer.json b/vendor/cache/tag-interop/composer.json
new file mode 100644
index 0000000..03dacbe
--- /dev/null
+++ b/vendor/cache/tag-interop/composer.json
@@ -0,0 +1,39 @@
+{
+ "name": "cache/tag-interop",
+ "description": "Framework interoperable interfaces for tags",
+ "license": "MIT",
+ "type": "library",
+ "keywords": [
+ "cache",
+ "psr6",
+ "tag",
+ "psr"
+ ],
+ "authors": [
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ },
+ {
+ "name": "Nicolas Grekas ",
+ "email": "p@tchwork.com",
+ "homepage": "https://github.com/nicolas-grekas"
+ }
+ ],
+ "homepage": "https://www.php-cache.com/en/latest/",
+ "require": {
+ "php": "^5.5 || ^7.0 || ^8.0",
+ "psr/cache": "^1.0 || ^2.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Cache\\TagInterop\\": ""
+ }
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ }
+}
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
index 5490b88..350f034 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -7,9 +7,22 @@ $baseDir = dirname($vendorDir);
return array(
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
+ 'Callback' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackBody' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackParam' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackParameterToReference' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackReturnReference' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackReturnValue' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+ 'DOMDocumentWrapper' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'DOMEvent' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'ICallbackNamed' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+ 'phpQuery' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'phpQueryEvents' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'phpQueryObject' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
+ 'phpQueryPlugins' => $vendorDir . '/jaeger/phpquery-single/phpQuery.php',
);
diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php
index db09430..f3d9ee5 100644
--- a/vendor/composer/autoload_files.php
+++ b/vendor/composer/autoload_files.php
@@ -14,10 +14,12 @@ return array(
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
- '6b998e7ad3182c0d21d23780badfa07b' => $vendorDir . '/yansongda/supports/src/Functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
- 'b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
+ 'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php',
+ 'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php',
+ '6b998e7ad3182c0d21d23780badfa07b' => $vendorDir . '/yansongda/supports/src/Functions.php',
+ 'b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php',
'223fa6f9b46fbe5d6b44c5ff847bfceb' => $vendorDir . '/taoser/think-addons/src/helper.php',
'1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php',
'8c783b3a3de2f6d9177022b5ccdcc841' => $vendorDir . '/yansongda/pay/src/Functions.php',
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index 8af5489..256a9d5 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -22,10 +22,12 @@ return array(
'Yansongda\\Supports\\' => array($vendorDir . '/yansongda/supports/src'),
'Yansongda\\Pay\\' => array($vendorDir . '/yansongda/pay/src'),
'Workerman\\' => array($vendorDir . '/workerman/workerman'),
+ 'Tightenco\\Collect\\' => array($vendorDir . '/tightenco/collect/src/Collect'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
+ 'QL\\' => array($vendorDir . '/jaeger/querylist/src'),
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
@@ -41,6 +43,7 @@ return array(
'League\\Flysystem\\Cached\\' => array($vendorDir . '/league/flysystem-cached-adapter/src'),
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
+ 'Jaeger\\' => array($vendorDir . '/jaeger/g-http/src'),
'Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
@@ -50,5 +53,8 @@ return array(
'DI\\' => array($vendorDir . '/php-di/php-di/src'),
'DASPRiD\\Enum\\' => array($vendorDir . '/dasprid/enum/src'),
'Channel\\' => array($vendorDir . '/workerman/channel/src'),
+ 'Cache\\TagInterop\\' => array($vendorDir . '/cache/tag-interop'),
+ 'Cache\\Adapter\\Filesystem\\' => array($vendorDir . '/cache/filesystem-adapter'),
+ 'Cache\\Adapter\\Common\\' => array($vendorDir . '/cache/adapter-common'),
'BaconQrCode\\' => array($vendorDir . '/bacon/bacon-qr-code/src'),
);
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 8adb947..3449295 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -15,10 +15,12 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
- '6b998e7ad3182c0d21d23780badfa07b' => __DIR__ . '/..' . '/yansongda/supports/src/Functions.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
- 'b33e3d135e5d9e47d845c576147bda89' => __DIR__ . '/..' . '/php-di/php-di/src/functions.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
+ 'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php',
+ 'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php',
+ '6b998e7ad3182c0d21d23780badfa07b' => __DIR__ . '/..' . '/yansongda/supports/src/Functions.php',
+ 'b33e3d135e5d9e47d845c576147bda89' => __DIR__ . '/..' . '/php-di/php-di/src/functions.php',
'223fa6f9b46fbe5d6b44c5ff847bfceb' => __DIR__ . '/..' . '/taoser/think-addons/src/helper.php',
'1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
'8c783b3a3de2f6d9177022b5ccdcc841' => __DIR__ . '/..' . '/yansongda/pay/src/Functions.php',
@@ -63,6 +65,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
array (
'Workerman\\' => 10,
),
+ 'T' =>
+ array (
+ 'Tightenco\\Collect\\' => 18,
+ ),
'S' =>
array (
'Symfony\\Polyfill\\Php80\\' => 23,
@@ -70,6 +76,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Component\\VarDumper\\' => 28,
),
+ 'Q' =>
+ array (
+ 'QL\\' => 3,
+ ),
'P' =>
array (
'Psr\\SimpleCache\\' => 16,
@@ -91,6 +101,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
'League\\Flysystem\\' => 17,
'Laravel\\SerializableClosure\\' => 28,
),
+ 'J' =>
+ array (
+ 'Jaeger\\' => 7,
+ ),
'I' =>
array (
'Invoker\\' => 8,
@@ -117,6 +131,9 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
'C' =>
array (
'Channel\\' => 8,
+ 'Cache\\TagInterop\\' => 17,
+ 'Cache\\Adapter\\Filesystem\\' => 25,
+ 'Cache\\Adapter\\Common\\' => 21,
),
'B' =>
array (
@@ -193,6 +210,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
array (
0 => __DIR__ . '/..' . '/workerman/workerman',
),
+ 'Tightenco\\Collect\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/tightenco/collect/src/Collect',
+ ),
'Symfony\\Polyfill\\Php80\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
@@ -209,6 +230,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
array (
0 => __DIR__ . '/..' . '/symfony/var-dumper',
),
+ 'QL\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/jaeger/querylist/src',
+ ),
'Psr\\SimpleCache\\' =>
array (
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
@@ -269,6 +294,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
array (
0 => __DIR__ . '/..' . '/laravel/serializable-closure/src',
),
+ 'Jaeger\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/jaeger/g-http/src',
+ ),
'Invoker\\' =>
array (
0 => __DIR__ . '/..' . '/php-di/invoker/src',
@@ -305,6 +334,18 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
array (
0 => __DIR__ . '/..' . '/workerman/channel/src',
),
+ 'Cache\\TagInterop\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/cache/tag-interop',
+ ),
+ 'Cache\\Adapter\\Filesystem\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/cache/filesystem-adapter',
+ ),
+ 'Cache\\Adapter\\Common\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/cache/adapter-common',
+ ),
'BaconQrCode\\' =>
array (
0 => __DIR__ . '/..' . '/bacon/bacon-qr-code/src',
@@ -317,11 +358,24 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
public static $classMap = array (
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
+ 'Callback' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackBody' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackParam' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackParameterToReference' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackReturnReference' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'CallbackReturnValue' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+ 'DOMDocumentWrapper' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'DOMEvent' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'ICallbackNamed' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
+ 'phpQuery' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'phpQueryEvents' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'phpQueryObject' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
+ 'phpQueryPlugins' => __DIR__ . '/..' . '/jaeger/phpquery-single/phpQuery.php',
);
public static function getInitializer(ClassLoader $loader)
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index d6f4c13..89cfec6 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -57,6 +57,210 @@
},
"install-path": "../bacon/bacon-qr-code"
},
+ {
+ "name": "cache/adapter-common",
+ "version": "1.3.0",
+ "version_normalized": "1.3.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-cache/adapter-common.git",
+ "reference": "8788309be72aa7be69b88cdc0687549c74a7d479"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-cache/adapter-common/zipball/8788309be72aa7be69b88cdc0687549c74a7d479",
+ "reference": "8788309be72aa7be69b88cdc0687549c74a7d479",
+ "shasum": ""
+ },
+ "require": {
+ "cache/tag-interop": "^1.0",
+ "php": ">=7.4",
+ "psr/cache": "^1.0 || ^2.0",
+ "psr/log": "^1.0 || ^2.0 || ^3.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "^0.17",
+ "phpunit/phpunit": "^7.5.20 || ^9.5.10"
+ },
+ "time": "2022-01-15T15:47:19+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Cache\\Adapter\\Common\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Scherer",
+ "email": "aequasi@gmail.com",
+ "homepage": "https://github.com/aequasi"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ }
+ ],
+ "description": "Common classes for PSR-6 adapters",
+ "homepage": "http://www.php-cache.com/en/latest/",
+ "keywords": [
+ "cache",
+ "psr-6",
+ "tag"
+ ],
+ "support": {
+ "source": "https://github.com/php-cache/adapter-common/tree/1.3.0"
+ },
+ "install-path": "../cache/adapter-common"
+ },
+ {
+ "name": "cache/filesystem-adapter",
+ "version": "1.2.0",
+ "version_normalized": "1.2.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-cache/filesystem-adapter.git",
+ "reference": "f1faaae40aaa696ef899cef6f6888aedb90b419b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-cache/filesystem-adapter/zipball/f1faaae40aaa696ef899cef6f6888aedb90b419b",
+ "reference": "f1faaae40aaa696ef899cef6f6888aedb90b419b",
+ "shasum": ""
+ },
+ "require": {
+ "cache/adapter-common": "^1.0",
+ "league/flysystem": "^1.0",
+ "php": ">=7.4",
+ "psr/cache": "^1.0 || ^2.0",
+ "psr/simple-cache": "^1.0"
+ },
+ "provide": {
+ "psr/cache-implementation": "^1.0",
+ "psr/simple-cache-implementation": "^1.0"
+ },
+ "require-dev": {
+ "cache/integration-tests": "^0.17",
+ "phpunit/phpunit": "^7.5.20 || ^9.5.10"
+ },
+ "time": "2022-01-15T15:47:19+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Cache\\Adapter\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Aaron Scherer",
+ "email": "aequasi@gmail.com",
+ "homepage": "https://github.com/aequasi"
+ },
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ }
+ ],
+ "description": "A PSR-6 cache implementation using filesystem. This implementation supports tags",
+ "homepage": "http://www.php-cache.com/en/latest/",
+ "keywords": [
+ "cache",
+ "filesystem",
+ "psr-6",
+ "tag"
+ ],
+ "support": {
+ "source": "https://github.com/php-cache/filesystem-adapter/tree/1.2.0"
+ },
+ "install-path": "../cache/filesystem-adapter"
+ },
+ {
+ "name": "cache/tag-interop",
+ "version": "1.1.0",
+ "version_normalized": "1.1.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-cache/tag-interop.git",
+ "reference": "b062b1d735357da50edf8387f7a8696f3027d328"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-cache/tag-interop/zipball/b062b1d735357da50edf8387f7a8696f3027d328",
+ "reference": "b062b1d735357da50edf8387f7a8696f3027d328",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^5.5 || ^7.0 || ^8.0",
+ "psr/cache": "^1.0 || ^2.0"
+ },
+ "time": "2021-12-31T10:03:23+00:00",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Cache\\TagInterop\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tobias Nyholm",
+ "email": "tobias.nyholm@gmail.com",
+ "homepage": "https://github.com/nyholm"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com",
+ "homepage": "https://github.com/nicolas-grekas"
+ }
+ ],
+ "description": "Framework interoperable interfaces for tags",
+ "homepage": "https://www.php-cache.com/en/latest/",
+ "keywords": [
+ "cache",
+ "psr",
+ "psr6",
+ "tag"
+ ],
+ "support": {
+ "issues": "https://github.com/php-cache/tag-interop/issues",
+ "source": "https://github.com/php-cache/tag-interop/tree/1.1.0"
+ },
+ "install-path": "../cache/tag-interop"
+ },
{
"name": "dasprid/enum",
"version": "1.0.3",
@@ -536,6 +740,163 @@
],
"install-path": "../guzzlehttp/psr7"
},
+ {
+ "name": "jaeger/g-http",
+ "version": "V1.7.2",
+ "version_normalized": "1.7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jae-jae/GHttp.git",
+ "reference": "82585ddd5e2c6651e37ab1d8166efcdbb6b293d4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jae-jae/GHttp/zipball/82585ddd5e2c6651e37ab1d8166efcdbb6b293d4",
+ "reference": "82585ddd5e2c6651e37ab1d8166efcdbb6b293d4",
+ "shasum": ""
+ },
+ "require": {
+ "cache/filesystem-adapter": "^1",
+ "guzzlehttp/guzzle": "^6.0 | ^7.0"
+ },
+ "time": "2021-08-08T04:59:44+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Jaeger\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaeger",
+ "email": "JaegerCode@gmail.com"
+ }
+ ],
+ "description": "Simple Http client base on GuzzleHttp",
+ "support": {
+ "issues": "https://github.com/jae-jae/GHttp/issues",
+ "source": "https://github.com/jae-jae/GHttp/tree/V1.7.2"
+ },
+ "install-path": "../jaeger/g-http"
+ },
+ {
+ "name": "jaeger/phpquery-single",
+ "version": "1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jae-jae/phpQuery-single.git",
+ "reference": "39a650ade692a6b480c22220dce0c198d6a946fb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jae-jae/phpQuery-single/zipball/39a650ade692a6b480c22220dce0c198d6a946fb",
+ "reference": "39a650ade692a6b480c22220dce0c198d6a946fb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "time": "2022-03-26T15:01:16+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "phpQuery.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Tobiasz Cudnik",
+ "email": "tobiasz.cudnik@gmail.com",
+ "homepage": "https://github.com/TobiaszCudnik",
+ "role": "Developer"
+ },
+ {
+ "name": "Jaeger",
+ "role": "Packager"
+ }
+ ],
+ "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/",
+ "homepage": "http://code.google.com/p/phpquery/",
+ "support": {
+ "issues": "https://github.com/jae-jae/phpQuery-single/issues",
+ "source": "https://github.com/jae-jae/phpQuery-single/tree/1.1.1"
+ },
+ "install-path": "../jaeger/phpquery-single"
+ },
+ {
+ "name": "jaeger/querylist",
+ "version": "V4.2.8",
+ "version_normalized": "4.2.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/jae-jae/QueryList.git",
+ "reference": "39dc0ca9c668bec7a793e20472ccd7d26ef89ea4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/jae-jae/QueryList/zipball/39dc0ca9c668bec7a793e20472ccd7d26ef89ea4",
+ "reference": "39dc0ca9c668bec7a793e20472ccd7d26ef89ea4",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "jaeger/g-http": "^1.1",
+ "jaeger/phpquery-single": "^1",
+ "php": ">=7.1",
+ "tightenco/collect": ">5.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5",
+ "symfony/var-dumper": "^3.3"
+ },
+ "time": "2021-07-05T06:07:58+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "QL\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaeger",
+ "email": "JaegerCode@gmail.com"
+ }
+ ],
+ "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。",
+ "homepage": "http://querylist.cc",
+ "keywords": [
+ "QueryList",
+ "phpQuery",
+ "spider"
+ ],
+ "support": {
+ "issues": "https://github.com/jae-jae/QueryList/issues",
+ "source": "https://github.com/jae-jae/QueryList/tree/V4.2.8"
+ },
+ "funding": [
+ {
+ "url": "https://opencollective.com/querylist",
+ "type": "open_collective"
+ }
+ ],
+ "install-path": "../jaeger/querylist"
+ },
{
"name": "laravel/serializable-closure",
"version": "v1.2.2",
@@ -2159,6 +2520,63 @@
},
"install-path": "../taoser/think-setarr"
},
+ {
+ "name": "tightenco/collect",
+ "version": "v8.83.23",
+ "version_normalized": "8.83.23.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/tighten/collect.git",
+ "reference": "a4423c6ace6b54ba4f86c0ac9de588c57bc94d79"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/tighten/collect/zipball/a4423c6ace6b54ba4f86c0ac9de588c57bc94d79",
+ "reference": "a4423c6ace6b54ba4f86c0ac9de588c57bc94d79",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.3|^8.0",
+ "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "nesbot/carbon": "^2.23.0",
+ "phpunit/phpunit": "^8.3"
+ },
+ "time": "2022-08-22T17:50:04+00:00",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "files": [
+ "src/Collect/Support/helpers.php",
+ "src/Collect/Support/alias.php"
+ ],
+ "psr-4": {
+ "Tightenco\\Collect\\": "src/Collect"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylorotwell@gmail.com"
+ }
+ ],
+ "description": "Collect - Illuminate Collections as a separate package.",
+ "keywords": [
+ "collection",
+ "laravel"
+ ],
+ "support": {
+ "issues": "https://github.com/tighten/collect/issues",
+ "source": "https://github.com/tighten/collect/tree/v8.83.23"
+ },
+ "install-path": "../tightenco/collect"
+ },
{
"name": "topthink/framework",
"version": "v6.0.13",
@@ -3075,10 +3493,6 @@
],
"dev": true,
"dev-package-names": [
- "symfony/polyfill-mbstring",
- "symfony/polyfill-php72",
- "symfony/polyfill-php80",
- "symfony/var-dumper",
"topthink/think-trace"
]
}
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 96b444e..a909776 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -3,7 +3,7 @@
'name' => 'taoser/taoler',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => '2f7a73366c80586c404f620fc440626d4c033930',
+ 'reference' => '846581c3abfa893a57d4ce930c2fa684dfd688af',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -19,6 +19,33 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'cache/adapter-common' => array(
+ 'pretty_version' => '1.3.0',
+ 'version' => '1.3.0.0',
+ 'reference' => '8788309be72aa7be69b88cdc0687549c74a7d479',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../cache/adapter-common',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'cache/filesystem-adapter' => array(
+ 'pretty_version' => '1.2.0',
+ 'version' => '1.2.0.0',
+ 'reference' => 'f1faaae40aaa696ef899cef6f6888aedb90b419b',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../cache/filesystem-adapter',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'cache/tag-interop' => array(
+ 'pretty_version' => '1.1.0',
+ 'version' => '1.1.0.0',
+ 'reference' => 'b062b1d735357da50edf8387f7a8696f3027d328',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../cache/tag-interop',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'dasprid/enum' => array(
'pretty_version' => '1.0.3',
'version' => '1.0.3.0',
@@ -73,6 +100,33 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'jaeger/g-http' => array(
+ 'pretty_version' => 'V1.7.2',
+ 'version' => '1.7.2.0',
+ 'reference' => '82585ddd5e2c6651e37ab1d8166efcdbb6b293d4',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../jaeger/g-http',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'jaeger/phpquery-single' => array(
+ 'pretty_version' => '1.1.1',
+ 'version' => '1.1.1.0',
+ 'reference' => '39a650ade692a6b480c22220dce0c198d6a946fb',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../jaeger/phpquery-single',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
+ 'jaeger/querylist' => array(
+ 'pretty_version' => 'V4.2.8',
+ 'version' => '4.2.8.0',
+ 'reference' => '39dc0ca9c668bec7a793e20472ccd7d26ef89ea4',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../jaeger/querylist',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'laravel/serializable-closure' => array(
'pretty_version' => 'v1.2.2',
'version' => '1.2.2.0',
@@ -172,6 +226,12 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'psr/cache-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '^1.0',
+ ),
+ ),
'psr/container' => array(
'pretty_version' => '1.1.2',
'version' => '1.1.2.0',
@@ -244,6 +304,12 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'psr/simple-cache-implementation' => array(
+ 'dev_requirement' => false,
+ 'provided' => array(
+ 0 => '^1.0',
+ ),
+ ),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
@@ -260,7 +326,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
- 'dev_requirement' => true,
+ 'dev_requirement' => false,
),
'symfony/polyfill-php72' => array(
'pretty_version' => 'v1.26.0',
@@ -269,7 +335,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php72',
'aliases' => array(),
- 'dev_requirement' => true,
+ 'dev_requirement' => false,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.26.0',
@@ -278,7 +344,7 @@
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
- 'dev_requirement' => true,
+ 'dev_requirement' => false,
),
'symfony/var-dumper' => array(
'pretty_version' => 'v4.4.44',
@@ -287,12 +353,12 @@
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/var-dumper',
'aliases' => array(),
- 'dev_requirement' => true,
+ 'dev_requirement' => false,
),
'taoser/taoler' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
- 'reference' => '2f7a73366c80586c404f620fc440626d4c033930',
+ 'reference' => '846581c3abfa893a57d4ce930c2fa684dfd688af',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -325,6 +391,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
+ 'tightenco/collect' => array(
+ 'pretty_version' => 'v8.83.23',
+ 'version' => '8.83.23.0',
+ 'reference' => 'a4423c6ace6b54ba4f86c0ac9de588c57bc94d79',
+ 'type' => 'library',
+ 'install_path' => __DIR__ . '/../tightenco/collect',
+ 'aliases' => array(),
+ 'dev_requirement' => false,
+ ),
'topthink/framework' => array(
'pretty_version' => 'v6.0.13',
'version' => '6.0.13.0',
diff --git a/vendor/jaeger/g-http/.gitignore b/vendor/jaeger/g-http/.gitignore
new file mode 100644
index 0000000..1541c56
--- /dev/null
+++ b/vendor/jaeger/g-http/.gitignore
@@ -0,0 +1,3 @@
+.idea
+/vendor/
+composer.lock
\ No newline at end of file
diff --git a/vendor/jaeger/g-http/README.md b/vendor/jaeger/g-http/README.md
new file mode 100644
index 0000000..f4cd602
--- /dev/null
+++ b/vendor/jaeger/g-http/README.md
@@ -0,0 +1,158 @@
+# GHttp
+基于GuzzleHttp的简单版Http客户端。 Simple Http client base on GuzzleHttp
+
+## 安装
+
+```
+composer require jaeger/g-http
+```
+
+## 用法
+
+#### 1. get / getJson
+```php
+
+use Jaeger\GHttp;
+
+$rt = GHttp::get('https://www.baidu.com/s?wd=QueryList');
+
+$rt = GHttp::get('https://www.baidu.com/s','wd=QueryList&wd2=teststr');
+
+//or
+
+$rt = GHttp::get('https://www.baidu.com/s',[
+ 'wd' => 'QueryList',
+ 'wd2' => 'teststr'
+]);
+
+//opt
+
+$rt = GHttp::get('https://www.baidu.com/s',[
+ 'wd' => 'QueryList'
+],[
+ 'headers' => [
+ 'referer' => 'https://baidu.com',
+ 'User-Agent' => 'Mozilla/5.0 (Windows NTChrome/58.0.3029.110 Safari/537.36',
+ 'Cookie' => 'cookie xxx'
+ ]
+]);
+
+$rt = GHttp::getJson('https://xxxx.com/json');
+
+```
+
+#### 2.post / postRaw / postJson
+```php
+$rt = GHttp::post('https://www.posttestserver.com/post.php',[
+ 'name' => 'QueryList',
+ 'password' => 'ql'
+]);
+
+$rt = GHttp::post('https://www.posttestserver.com/post.php','name=QueryList&password=ql');
+
+
+$rt = GHttp::postRaw('http://httpbin.org/post','raw data');
+$rt = GHttp::postRaw('http://httpbin.org/post',['aa' => 11,'bb' => 22]);
+
+
+$rt = GHttp::postJson('http://httpbin.org/post',['aa' => 11,'bb' => 22]);
+$rt = GHttp::postJson('http://httpbin.org/post','aa=11&bb=22');
+
+```
+#### 3.download
+
+```php
+GHttp::download('http://sw.bos.baidu.com/setup.exe','./path/to/xx.exe');
+```
+### 4. concurrent requests
+```php
+use Jaeger\GHttp;
+
+$urls = [
+ 'http://httpbin.org/get?name=php',
+ 'http://httpbin.org/get?name=go',
+ 'http://httpbin.org/get?name=c#',
+ 'http://httpbin.org/get?name=java'
+];
+
+GHttp::multiRequest($urls)->withHeaders([
+ 'X-Powered-By' => 'Jaeger'
+])->withOptions([
+ 'timeout' => 10
+])->concurrency(2)->success(function($response,$index){
+ print_r((String)$response->getBody());
+ print_r($index);
+})->error(function($reason,$index){
+ print_r($reason);
+})->get();
+```
+
+```php
+use Jaeger\GHttp;
+use GuzzleHttp\Psr7\Request;
+
+$requests = [
+ new Request('POST','http://httpbin.org/post',[
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'User-Agent' => 'g-http'
+ ],http_build_query([
+ 'name' => 'php'
+ ])),
+ new Request('POST','http://httpbin.org/post',[
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'User-Agent' => 'g-http'
+ ],http_build_query([
+ 'name' => 'go'
+ ])),
+ new Request('POST','http://httpbin.org/post',[
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'User-Agent' => 'g-http'
+ ],http_build_query([
+ 'name' => 'c#'
+ ]))
+];
+
+GHttp::multiRequest($requests)->success(function($response,$index){
+ print_r((String)$response->getBody());
+ print_r($index);
+})->post();
+```
+### 5. Request with cache
+
+Base on PHP-Cache: http://www.php-cache.com
+
+- Use filesystem cache
+```php
+use Jaeger\GHttp;
+
+$rt = GHttp::get('http://httpbin.org/get',[
+ 'wd' => 'QueryList'
+],[
+ 'cache' => __DIR__,
+ 'cache_ttl' => 120 //seconds
+]);
+
+```
+
+- Use predis cache
+
+Install predis adapter:
+```
+composer require cache/predis-adapter
+```
+
+Usage:
+```php
+use Jaeger\GHttp;
+use Cache\Adapter\Predis\PredisCachePool;
+
+$client = new \Predis\Client('tcp:/127.0.0.1:6379');
+$pool = new PredisCachePool($client);
+
+$rt = GHttp::get('http://httpbin.org/get',[
+ 'wd' => 'QueryList'
+],[
+ 'cache' => $pool,
+ 'cache_ttl' => 120 //seconds
+]);
+```
\ No newline at end of file
diff --git a/vendor/jaeger/g-http/composer.json b/vendor/jaeger/g-http/composer.json
new file mode 100644
index 0000000..6781037
--- /dev/null
+++ b/vendor/jaeger/g-http/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "jaeger/g-http",
+ "description": "Simple Http client base on GuzzleHttp",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jaeger",
+ "email": "JaegerCode@gmail.com"
+ }
+ ],
+ "require": {
+ "cache/filesystem-adapter": "^1",
+ "guzzlehttp/guzzle": "^6.0 | ^7.0"
+ },
+ "autoload":{
+ "psr-4":{
+ "Jaeger\\":"src"
+ }
+ }
+}
diff --git a/vendor/jaeger/g-http/examples/multi_request.php b/vendor/jaeger/g-http/examples/multi_request.php
new file mode 100644
index 0000000..b2a138d
--- /dev/null
+++ b/vendor/jaeger/g-http/examples/multi_request.php
@@ -0,0 +1,27 @@
+
+ * Date: 18/12/10
+ * Time: 下午4:04
+ */
+require __DIR__.'/../vendor/autoload.php';
+use Jaeger\GHttp;
+
+$urls = [
+ 'http://httpbin.org/get?name=php',
+ 'http://httpbin.org/get?name=go',
+ 'http://httpbin.org/get?name=c#',
+ 'http://httpbin.org/get?name=java'
+];
+
+GHttp::multiRequest($urls)->withHeaders([
+ 'X-Powered-By' => 'Jaeger'
+])->withOptions([
+ 'timeout' => 10
+])->concurrency(2)->success(function($response,$index){
+ print_r((String)$response->getBody());
+ print_r($index);
+})->error(function($reason,$index){
+ print_r($reason);
+})->get();
\ No newline at end of file
diff --git a/vendor/jaeger/g-http/examples/multi_request_2.php b/vendor/jaeger/g-http/examples/multi_request_2.php
new file mode 100644
index 0000000..d66612b
--- /dev/null
+++ b/vendor/jaeger/g-http/examples/multi_request_2.php
@@ -0,0 +1,37 @@
+
+ * Date: 18/12/10
+ * Time: 下午6:51
+ */
+
+require __DIR__.'/../vendor/autoload.php';
+use Jaeger\GHttp;
+use GuzzleHttp\Psr7\Request;
+
+$requests = [
+ new Request('POST','http://httpbin.org/post',[
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'User-Agent' => 'g-http'
+ ],http_build_query([
+ 'name' => 'php'
+ ])),
+ new Request('POST','http://httpbin.org/post',[
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'User-Agent' => 'g-http'
+ ],http_build_query([
+ 'name' => 'go'
+ ])),
+ new Request('POST','http://httpbin.org/post',[
+ 'Content-Type' => 'application/x-www-form-urlencoded',
+ 'User-Agent' => 'g-http'
+ ],http_build_query([
+ 'name' => 'c#'
+ ]))
+];
+
+GHttp::multiRequest($requests)->success(function($response,$index){
+ print_r((String)$response->getBody());
+ print_r($index);
+})->post();
\ No newline at end of file
diff --git a/vendor/jaeger/g-http/examples/request_with_cache.php b/vendor/jaeger/g-http/examples/request_with_cache.php
new file mode 100644
index 0000000..cdefdbb
--- /dev/null
+++ b/vendor/jaeger/g-http/examples/request_with_cache.php
@@ -0,0 +1,33 @@
+
+ * Date: 18/12/11
+ * Time: 下午6:48
+ */
+
+require __DIR__.'/../vendor/autoload.php';
+use Jaeger\GHttp;
+use Cache\Adapter\Predis\PredisCachePool;
+
+
+$rt = GHttp::get('http://httpbin.org/get',[
+ 'wd' => 'QueryList'
+],[
+ 'cache' => __DIR__,
+ 'cache_ttl' => 120
+]);
+
+print_r($rt);
+
+$client = new \Predis\Client('tcp:/127.0.0.1:6379');
+$pool = new PredisCachePool($client);
+
+$rt = GHttp::get('http://httpbin.org/get',[
+ 'wd' => 'QueryList'
+],[
+ 'cache' => $pool,
+ 'cache_ttl' => 120
+]);
+
+print_r($rt);
\ No newline at end of file
diff --git a/vendor/jaeger/g-http/examples/simple.php b/vendor/jaeger/g-http/examples/simple.php
new file mode 100644
index 0000000..df65510
--- /dev/null
+++ b/vendor/jaeger/g-http/examples/simple.php
@@ -0,0 +1,22 @@
+
+ * Date: 18/12/11
+ * Time: 下午6:48
+ */
+
+require __DIR__.'/../vendor/autoload.php';
+use Jaeger\GHttp;
+
+$rt = GHttp::get('http://httpbin.org/get',[
+ 'wd' => 'QueryList'
+],[
+ 'headers' => [
+ 'referer' => 'https://baidu.com',
+ 'User-Agent' => 'Mozilla/5.0 (Windows NTChrome/58.0.3029.110 Safari/537.36',
+ 'Cookie' => 'cookie xxx'
+ ],
+]);
+
+print_r($rt);
diff --git a/vendor/jaeger/g-http/src/Cache.php b/vendor/jaeger/g-http/src/Cache.php
new file mode 100644
index 0000000..f163a6e
--- /dev/null
+++ b/vendor/jaeger/g-http/src/Cache.php
@@ -0,0 +1,65 @@
+
+ * Date: 18/12/11
+ * Time: 下午6:39
+ */
+
+namespace Jaeger;
+
+
+use Cache\Adapter\Common\AbstractCachePool;
+use Cache\Adapter\Filesystem\FilesystemCachePool;
+use League\Flysystem\Adapter\Local;
+use League\Flysystem\Filesystem;
+
+class Cache extends GHttp
+{
+ public static function remember($name, $arguments)
+ {
+ $cachePool = null;
+ $cacheConfig = self::initCacheConfig($arguments);
+
+ if (empty($cacheConfig['cache'])) {
+ return self::$name(...$arguments);
+ }
+ if (is_string($cacheConfig['cache'])) {
+ $filesystemAdapter = new Local($cacheConfig['cache']);
+ $filesystem = new Filesystem($filesystemAdapter);
+ $cachePool = new FilesystemCachePool($filesystem);
+ }else if ($cacheConfig['cache'] instanceof AbstractCachePool) {
+ $cachePool = $cacheConfig['cache'];
+ }
+
+ $cacheKey = self::getCacheKey($name,$arguments);
+ $data = $cachePool->get($cacheKey);
+ if(empty($data)) {
+ $data = self::$name(...$arguments);
+ if(!empty($data)) {
+ $cachePool->set($cacheKey,$data,$cacheConfig['cache_ttl']);
+ }
+ }
+ return $data;
+ }
+
+ protected static function initCacheConfig($arguments)
+ {
+ $cacheConfig = [
+ 'cache' => null,
+ 'cache_ttl' => null
+ ];
+ if(!empty($arguments[2])) {
+ $cacheConfig = array_merge([
+ 'cache' => null,
+ 'cache_ttl' => null
+ ],$arguments[2]);
+ }
+ return $cacheConfig;
+ }
+
+ protected static function getCacheKey($name, $arguments)
+ {
+ return md5($name.'_'.json_encode($arguments));
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/g-http/src/GHttp.php b/vendor/jaeger/g-http/src/GHttp.php
new file mode 100644
index 0000000..8daf808
--- /dev/null
+++ b/vendor/jaeger/g-http/src/GHttp.php
@@ -0,0 +1,164 @@
+
+ *
+ * @Version V1.0
+ */
+
+namespace Jaeger;
+
+use GuzzleHttp\Client;
+
+/**
+ * Class GHttp
+ * @package Jaeger
+ *
+ * @method static string get($url,$args = null,$otherArgs = [])
+ * @method static mixed getJson($url, $args = null, $otherArgs = [])
+ * @method static string post($url,$args = null,$otherArgs = [])
+ * @method static string postRaw($url, $raw = null, $otherArgs = [])
+ * @method static string postJson($url, $args = null, $otherArgs = [])
+ */
+class GHttp
+{
+ private static $client = null;
+
+ public static function __callStatic($name, $arguments)
+ {
+ $protectedName = '_'.$name;
+ if(method_exists(self::class,$protectedName)){
+ return Cache::remember($protectedName, $arguments);
+ }
+ throw new MethodNotFoundException('Call undefined method '.self::class.':'.$name.'()');
+ }
+
+ public static function getClient(array $config = [])
+ {
+ if(self::$client == null){
+ self::$client = new Client($config);
+ }
+ return self::$client;
+ }
+
+ /**
+ * @param $url
+ * @param array $args
+ * @param array $otherArgs
+ * @return string
+ */
+ protected static function _get($url,$args = null,$otherArgs = [])
+ {
+ is_string($args) && parse_str($args,$args);
+ $args = array_merge([
+ 'verify' => false,
+ 'query' => $args,
+ 'headers' => [
+ 'referer' => $url,
+ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
+ ]
+ ],$otherArgs);
+ $client = self::getClient();
+ $response = $client->request('GET', $url,$args);
+ return (string)$response->getBody();
+ }
+
+ protected static function _getJson($url, $args = null, $otherArgs = [])
+ {
+ $data = self::get($url, $args , $otherArgs);
+ return json_decode($data,JSON_UNESCAPED_UNICODE);
+ }
+
+ /**
+ * @param $url
+ * @param array $args
+ * @param array $otherArgs
+ * @return string
+ */
+ protected static function _post($url,$args = null,$otherArgs = [])
+ {
+ is_string($args) && parse_str($args,$args);
+ $args = array_merge([
+ 'verify' => false,
+ 'form_params' => $args,
+ 'headers' => [
+ 'referer' => $url,
+ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
+ ]
+ ],$otherArgs);
+ $client = self::getClient();
+ $response = $client->request('Post', $url,$args);
+ return (string)$response->getBody();
+ }
+
+ /**
+ * @param $url
+ * @param null $raw
+ * @param array $otherArgs
+ * @return string
+ */
+ protected static function _postRaw($url, $raw = null, $otherArgs = [])
+ {
+ is_array($raw) && $raw = json_encode($raw);
+ $args = array_merge([
+ 'verify' => false,
+ 'body' => $raw,
+ 'headers' => [
+ 'referer' => $url,
+ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
+ ]
+ ],$otherArgs);
+ $client = self::getClient();
+ $response = $client->request('Post', $url,$args);
+ return (string)$response->getBody();
+ }
+
+ /**
+ * @param $url
+ * @param null $args
+ * @param array $otherArgs
+ * @return string
+ */
+ protected static function _postJson($url, $args = null, $otherArgs = [])
+ {
+ is_string($args) && parse_str($args,$args);
+ $args = array_merge([
+ 'verify' => false,
+ 'json' => $args,
+ 'headers' => [
+ 'referer' => $url,
+ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
+ ]
+ ],$otherArgs);
+ $client = self::getClient();
+ $response = $client->request('Post', $url,$args);
+ return (string)$response->getBody();
+ }
+
+ /**
+ * @param $url
+ * @param $filePath
+ * @param null $args
+ * @param array $otherArgs
+ * @return string
+ */
+ public static function download($url,$filePath,$args = null,$otherArgs = [])
+ {
+ $otherArgs = array_merge($otherArgs,[
+ 'sink' => $filePath,
+ ]);
+ return self::get($url,$args,$otherArgs);
+ }
+
+ /**
+ * @param $urls
+ * @return MultiRequest
+ */
+ public static function multiRequest($urls)
+ {
+ $client = self::getClient();
+ return MultiRequest::newRequest($client)->urls($urls);
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/g-http/src/MethodNotFoundException.php b/vendor/jaeger/g-http/src/MethodNotFoundException.php
new file mode 100644
index 0000000..dc6e527
--- /dev/null
+++ b/vendor/jaeger/g-http/src/MethodNotFoundException.php
@@ -0,0 +1,19 @@
+
+ * Date: 18/12/12
+ * Time: 上午10:56
+ */
+
+namespace Jaeger;
+use Exception;
+use Throwable;
+
+class MethodNotFoundException extends Exception
+{
+ public function __construct($message = "", $code = 0, Throwable $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/g-http/src/MultiRequest.php b/vendor/jaeger/g-http/src/MultiRequest.php
new file mode 100644
index 0000000..d77fc7f
--- /dev/null
+++ b/vendor/jaeger/g-http/src/MultiRequest.php
@@ -0,0 +1,113 @@
+
+ * Date: 18/12/10
+ * Time: 下午6:04
+ */
+
+namespace Jaeger;
+use GuzzleHttp\Client;
+use Closure;
+use GuzzleHttp\Pool;
+use GuzzleHttp\Psr7\Request;
+
+class MultiRequest
+{
+ protected $client;
+ protected $headers = [];
+ protected $options = [];
+ protected $successCallback;
+ protected $errorCallback;
+ protected $urls = [];
+ protected $method;
+ protected $concurrency = 5;
+
+ public function __construct(Client $client)
+ {
+ $this->client = $client;
+ $this->headers = [
+ 'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'
+ ];
+ }
+
+ public static function newRequest(Client $client)
+ {
+ $request = new self($client);
+ return $request;
+ }
+
+ public function withHeaders($headers)
+ {
+ $this->headers = array_merge($this->headers,$headers);
+ return $this;
+ }
+
+ public function withOptions($options)
+ {
+ $this->options = $options;
+ return $this;
+ }
+
+ public function concurrency($concurrency)
+ {
+ $this->concurrency = $concurrency;
+ return $this;
+ }
+
+ public function success(Closure $success)
+ {
+ $this->successCallback = $success;
+ return $this;
+ }
+
+ public function error(Closure $error)
+ {
+ $this->errorCallback = $error;
+ return $this;
+ }
+
+ public function urls(array $urls)
+ {
+ $this->urls = $urls;
+ return $this;
+ }
+
+ public function get()
+ {
+ $this->method = 'GET';
+ $this->send();
+ }
+
+ public function post()
+ {
+ $this->method = 'POST';
+ $this->send();
+ }
+
+ protected function send()
+ {
+ $client = $this->client;
+
+ $requests = function ($urls) use($client){
+ foreach ($urls as $url) {
+ if (is_string($url)) {
+ yield new Request($this->method,$url,$this->headers);
+ } else {
+ yield $url;
+ }
+ }
+ };
+
+ $pool = new Pool($client, $requests($this->urls), [
+ 'concurrency' => $this->concurrency,
+ 'fulfilled' => $this->successCallback,
+ 'rejected' => $this->errorCallback,
+ 'options' => $this->options
+ ]);
+
+ $promise = $pool->promise();
+ $promise->wait();
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/jaeger/phpquery-single/README.md b/vendor/jaeger/phpquery-single/README.md
new file mode 100644
index 0000000..903163e
--- /dev/null
+++ b/vendor/jaeger/phpquery-single/README.md
@@ -0,0 +1,36 @@
+# phpQuery-single
+phpQuery onefile composer.Continuous maintenance,Welcome PR.
+
+`QueryList` base on phpQuery: https://github.com/jae-jae/QueryList
+
+phpQuery单文件版本,持续维护,欢迎PR.
+> phpQuery项目主页:http://code.google.com/p/phpquery/
+
+`QueryList`是基于phpQuery的采集工具: https://github.com/jae-jae/QueryList
+
+## Composer Installation
+Packagist: https://packagist.org/packages/jaeger/phpquery-single
+```
+composer require jaeger/phpquery-single
+```
+
+## Usage
+```php
+$html = <<
+
+ 其它的一些 文本
+
+STR;
+
+$doc = phpQuery::newDocumentHTML($html);
+
+$src = $doc->find('.two img:eq(0)')->attr('src');
+
+echo $src;
+// http://querylist.cc/1.jpg
+```
\ No newline at end of file
diff --git a/vendor/jaeger/phpquery-single/composer.json b/vendor/jaeger/phpquery-single/composer.json
new file mode 100644
index 0000000..24fe5a4
--- /dev/null
+++ b/vendor/jaeger/phpquery-single/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "jaeger/phpquery-single",
+ "description": "phpQuery单文件版本,是Querylist的依赖(http://querylist.cc/),phpQuery项目主页:http://code.google.com/p/phpquery/",
+ "homepage": "http://code.google.com/p/phpquery/",
+ "license": "MIT",
+ "require": {
+ "PHP":">=5.3.0"
+ },
+ "authors": [
+ {
+ "name": "Tobiasz Cudnik"
+ ,"email": "tobiasz.cudnik@gmail.com"
+ ,"homepage": "https://github.com/TobiaszCudnik"
+ ,"role": "Developer"
+ }
+ ,{
+ "name": "Jaeger",
+ "role": "Packager"
+ }
+ ],
+ "autoload":{
+ "classmap":["phpQuery.php"]
+ }
+}
diff --git a/vendor/jaeger/phpquery-single/phpQuery.php b/vendor/jaeger/phpquery-single/phpQuery.php
new file mode 100644
index 0000000..90391cd
--- /dev/null
+++ b/vendor/jaeger/phpquery-single/phpQuery.php
@@ -0,0 +1,6086 @@
+
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @package phpQuery
+ */
+
+// class names for instanceof
+// TODO move them as class constants into phpQuery
+define('DOMDOCUMENT', 'DOMDocument');
+define('DOMELEMENT', 'DOMElement');
+define('DOMNODELIST', 'DOMNodeList');
+define('DOMNODE', 'DOMNode');
+
+/**
+ * DOMEvent class.
+ *
+ * Based on
+ * @link http://developer.mozilla.org/En/DOM:event
+ * @author Tobiasz Cudnik
+ * @package phpQuery
+ * @todo implement ArrayAccess ?
+ */
+class DOMEvent
+{
+ /**
+ * Returns a boolean indicating whether the event bubbles up through the DOM or not.
+ *
+ * @var unknown_type
+ */
+ public $bubbles = true;
+ /**
+ * Returns a boolean indicating whether the event is cancelable.
+ *
+ * @var unknown_type
+ */
+ public $cancelable = true;
+ /**
+ * Returns a reference to the currently registered target for the event.
+ *
+ * @var unknown_type
+ */
+ public $currentTarget;
+ /**
+ * Returns detail about the event, depending on the type of event.
+ *
+ * @var unknown_type
+ * @link http://developer.mozilla.org/en/DOM/event.detail
+ */
+ public $detail; // ???
+ /**
+ * Used to indicate which phase of the event flow is currently being evaluated.
+ *
+ * NOT IMPLEMENTED
+ *
+ * @var unknown_type
+ * @link http://developer.mozilla.org/en/DOM/event.eventPhase
+ */
+ public $eventPhase; // ???
+ /**
+ * The explicit original target of the event (Mozilla-specific).
+ *
+ * NOT IMPLEMENTED
+ *
+ * @var unknown_type
+ */
+ public $explicitOriginalTarget; // moz only
+ /**
+ * The original target of the event, before any retargetings (Mozilla-specific).
+ *
+ * NOT IMPLEMENTED
+ *
+ * @var unknown_type
+ */
+ public $originalTarget; // moz only
+ /**
+ * Identifies a secondary target for the event.
+ *
+ * @var unknown_type
+ */
+ public $relatedTarget;
+ /**
+ * Returns a reference to the target to which the event was originally dispatched.
+ *
+ * @var unknown_type
+ */
+ public $target;
+ /**
+ * Returns the time that the event was created.
+ *
+ * @var unknown_type
+ */
+ public $timeStamp;
+ /**
+ * Returns the name of the event (case-insensitive).
+ */
+ public $type;
+ public $runDefault = true;
+ public $data = null;
+ public function __construct($data)
+ {
+ foreach ($data as $k => $v) {
+ $this->$k = $v;
+ }
+ if (!$this->timeStamp)
+ $this->timeStamp = time();
+ }
+ /**
+ * Cancels the event (if it is cancelable).
+ *
+ */
+ public function preventDefault()
+ {
+ $this->runDefault = false;
+ }
+ /**
+ * Stops the propagation of events further along in the DOM.
+ *
+ */
+ public function stopPropagation()
+ {
+ $this->bubbles = false;
+ }
+}
+
+
+/**
+ * DOMDocumentWrapper class simplifies work with DOMDocument.
+ *
+ * Know bug:
+ * - in XHTML fragments, changes to
+ *
+ * @todo check XML catalogs compatibility
+ * @author Tobiasz Cudnik
+ * @package phpQuery
+ */
+class DOMDocumentWrapper
+{
+ /**
+ * @var DOMDocument
+ */
+ public $document;
+ public $id;
+ /**
+ * @todo Rewrite as method and quess if null.
+ * @var unknown_type
+ */
+ public $contentType = '';
+ public $xpath;
+ public $uuid = 0;
+ public $data = array();
+ public $dataNodes = array();
+ public $events = array();
+ public $eventsNodes = array();
+ public $eventsGlobal = array();
+ /**
+ * @TODO iframes support http://code.google.com/p/phpquery/issues/detail?id=28
+ * @var unknown_type
+ */
+ public $frames = array();
+ /**
+ * Document root, by default equals to document itself.
+ * Used by documentFragments.
+ *
+ * @var DOMNode
+ */
+ public $root;
+ public $isDocumentFragment;
+ public $isXML = false;
+ public $isXHTML = false;
+ public $isHTML = false;
+ public $charset;
+ public function __construct($markup = null, $contentType = null, $newDocumentID = null)
+ {
+ if (isset($markup))
+ $this->load($markup, $contentType, $newDocumentID);
+ $this->id = $newDocumentID
+ ? $newDocumentID
+ : md5(microtime());
+ }
+ public function load($markup, $contentType = null, $newDocumentID = null)
+ {
+ // phpQuery::$documents[$id] = $this;
+ $this->contentType = strtolower($contentType);
+ if ($markup instanceof DOMDOCUMENT) {
+ $this->document = $markup;
+ $this->root = $this->document;
+ $this->charset = $this->document->encoding;
+ // TODO isDocumentFragment
+ $loaded = true;
+ } else {
+ $loaded = $this->loadMarkup($markup);
+ }
+ if ($loaded) {
+ // $this->document->formatOutput = true;
+ $this->document->preserveWhiteSpace = true;
+ $this->xpath = new DOMXPath($this->document);
+ $this->afterMarkupLoad();
+ return true;
+ // remember last loaded document
+ // return phpQuery::selectDocument($id);
+ }
+ return false;
+ }
+ protected function afterMarkupLoad()
+ {
+ if ($this->isXHTML) {
+ $this->xpath->registerNamespace("html", "http://www.w3.org/1999/xhtml");
+ }
+ }
+ protected function loadMarkup($markup)
+ {
+ $loaded = false;
+ if ($this->contentType) {
+ self::debug("Load markup for content type {$this->contentType}");
+ // content determined by contentType
+ list($contentType, $charset) = $this->contentTypeToArray($this->contentType);
+ switch ($contentType) {
+ case 'text/html':
+ phpQuery::debug("Loading HTML, content type '{$this->contentType}'");
+ $loaded = $this->loadMarkupHTML($markup, $charset);
+ break;
+ case 'text/xml':
+ case 'application/xhtml+xml':
+ phpQuery::debug("Loading XML, content type '{$this->contentType}'");
+ $loaded = $this->loadMarkupXML($markup, $charset);
+ break;
+ default:
+ // for feeds or anything that sometimes doesn't use text/xml
+ if (strpos('xml', $this->contentType) !== false) {
+ phpQuery::debug("Loading XML, content type '{$this->contentType}'");
+ $loaded = $this->loadMarkupXML($markup, $charset);
+ } else
+ phpQuery::debug("Could not determine document type from content type '{$this->contentType}'");
+ }
+ } else {
+ // content type autodetection
+ if ($this->isXML($markup)) {
+ phpQuery::debug("Loading XML, isXML() == true");
+ $loaded = $this->loadMarkupXML($markup);
+ if (!$loaded && $this->isXHTML) {
+ phpQuery::debug('Loading as XML failed, trying to load as HTML, isXHTML == true');
+ $loaded = $this->loadMarkupHTML($markup);
+ }
+ } else {
+ phpQuery::debug("Loading HTML, isXML() == false");
+ $loaded = $this->loadMarkupHTML($markup);
+ }
+ }
+ return $loaded;
+ }
+ protected function loadMarkupReset()
+ {
+ $this->isXML = $this->isXHTML = $this->isHTML = false;
+ }
+ protected function documentCreate($charset, $version = '1.0')
+ {
+ if (!$version)
+ $version = '1.0';
+ $this->document = new DOMDocument($version, $charset);
+ $this->charset = $this->document->encoding;
+ // $this->document->encoding = $charset;
+ $this->document->formatOutput = true;
+ $this->document->preserveWhiteSpace = true;
+ }
+ protected function loadMarkupHTML($markup, $requestedCharset = null)
+ {
+ if (phpQuery::$debug)
+ phpQuery::debug('Full markup load (HTML): ' . substr($markup, 0, 250));
+ $this->loadMarkupReset();
+ $this->isHTML = true;
+ if (!isset($this->isDocumentFragment))
+ $this->isDocumentFragment = self::isDocumentFragmentHTML($markup);
+ $charset = null;
+ $documentCharset = $this->charsetFromHTML($markup);
+ $addDocumentCharset = false;
+ if ($documentCharset) {
+ $charset = $documentCharset;
+ $markup = $this->charsetFixHTML($markup);
+ } else if ($requestedCharset) {
+ $charset = $requestedCharset;
+ }
+ if (!$charset)
+ $charset = phpQuery::$defaultCharset;
+ // HTTP 1.1 says that the default charset is ISO-8859-1
+ // @see http://www.w3.org/International/O-HTTP-charset
+ if (!$documentCharset) {
+ $documentCharset = 'ISO-8859-1';
+ $addDocumentCharset = true;
+ }
+ // Should be careful here, still need 'magic encoding detection' since lots of pages have other 'default encoding'
+ // Worse, some pages can have mixed encodings... we'll try not to worry about that
+ $requestedCharset = $requestedCharset ? strtoupper($requestedCharset) : "";
+ $documentCharset = strtoupper($documentCharset);
+ phpQuery::debug("DOC: $documentCharset REQ: $requestedCharset");
+ if ($requestedCharset && $documentCharset && $requestedCharset !== $documentCharset) {
+ phpQuery::debug("CHARSET CONVERT");
+ // Document Encoding Conversion
+ // http://code.google.com/p/phpquery/issues/detail?id=86
+ if (function_exists('mb_detect_encoding')) {
+ $possibleCharsets = array($documentCharset, $requestedCharset, 'AUTO');
+ $docEncoding = mb_detect_encoding($markup, implode(', ', $possibleCharsets));
+ if (!$docEncoding)
+ $docEncoding = $documentCharset; // ok trust the document
+ phpQuery::debug("DETECTED '$docEncoding'");
+ // Detected does not match what document says...
+ if ($docEncoding !== $documentCharset) {
+ // Tricky..
+ }
+ if ($docEncoding !== $requestedCharset) {
+ phpQuery::debug("CONVERT $docEncoding => $requestedCharset");
+ $markup = mb_convert_encoding($markup, $requestedCharset, $docEncoding);
+ $markup = $this->charsetAppendToHTML($markup, $requestedCharset);
+ $charset = $requestedCharset;
+ }
+ } else {
+ phpQuery::debug("TODO: charset conversion without mbstring...");
+ }
+ }
+ $return = false;
+ if ($this->isDocumentFragment) {
+ phpQuery::debug("Full markup load (HTML), DocumentFragment detected, using charset '$charset'");
+ $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
+ } else {
+ if ($addDocumentCharset) {
+ phpQuery::debug("Full markup load (HTML), appending charset: '$charset'");
+ $markup = $this->charsetAppendToHTML($markup, $charset);
+ }
+ phpQuery::debug("Full markup load (HTML), documentCreate('$charset')");
+ $this->documentCreate($charset);
+ $return = phpQuery::$debug === 2
+ ? $this->document->loadHTML($markup)
+ : @$this->document->loadHTML($markup);
+ if ($return)
+ $this->root = $this->document;
+ }
+ if ($return && !$this->contentType)
+ $this->contentType = 'text/html';
+ return $return;
+ }
+ protected function loadMarkupXML($markup, $requestedCharset = null)
+ {
+ if (phpQuery::$debug)
+ phpQuery::debug('Full markup load (XML): ' . substr($markup, 0, 250));
+ $this->loadMarkupReset();
+ $this->isXML = true;
+ // check agains XHTML in contentType or markup
+ $isContentTypeXHTML = $this->isXHTML();
+ $isMarkupXHTML = $this->isXHTML($markup);
+ if ($isContentTypeXHTML || $isMarkupXHTML) {
+ self::debug('Full markup load (XML), XHTML detected');
+ $this->isXHTML = true;
+ }
+ // determine document fragment
+ if (!isset($this->isDocumentFragment))
+ $this->isDocumentFragment = $this->isXHTML
+ ? self::isDocumentFragmentXHTML($markup)
+ : self::isDocumentFragmentXML($markup);
+ // this charset will be used
+ $charset = null;
+ // charset from XML declaration @var string
+ $documentCharset = $this->charsetFromXML($markup);
+ if (!$documentCharset) {
+ if ($this->isXHTML) {
+ // this is XHTML, try to get charset from content-type meta header
+ $documentCharset = $this->charsetFromHTML($markup);
+ if ($documentCharset) {
+ phpQuery::debug("Full markup load (XML), appending XHTML charset '$documentCharset'");
+ $this->charsetAppendToXML($markup, $documentCharset);
+ $charset = $documentCharset;
+ }
+ }
+ if (!$documentCharset) {
+ // if still no document charset...
+ $charset = $requestedCharset;
+ }
+ } else if ($requestedCharset) {
+ $charset = $requestedCharset;
+ }
+ if (!$charset) {
+ $charset = phpQuery::$defaultCharset;
+ }
+ if ($requestedCharset && $documentCharset && $requestedCharset != $documentCharset) {
+ // TODO place for charset conversion
+ // $charset = $requestedCharset;
+ }
+ $return = false;
+ if ($this->isDocumentFragment) {
+ phpQuery::debug("Full markup load (XML), DocumentFragment detected, using charset '$charset'");
+ $return = $this->documentFragmentLoadMarkup($this, $charset, $markup);
+ } else {
+ // FIXME ???
+ if ($isContentTypeXHTML && !$isMarkupXHTML)
+ if (!$documentCharset) {
+ phpQuery::debug("Full markup load (XML), appending charset '$charset'");
+ $markup = $this->charsetAppendToXML($markup, $charset);
+ }
+ // see http://pl2.php.net/manual/en/book.dom.php#78929
+ // LIBXML_DTDLOAD (>= PHP 5.1)
+ // does XML ctalogues works with LIBXML_NONET
+ // $this->document->resolveExternals = true;
+ // TODO test LIBXML_COMPACT for performance improvement
+ // create document
+ $this->documentCreate($charset);
+ if (phpversion() < 5.1) {
+ $this->document->resolveExternals = true;
+ $return = phpQuery::$debug === 2
+ ? $this->document->loadXML($markup)
+ : @$this->document->loadXML($markup);
+ } else {
+ /** @link http://pl2.php.net/manual/en/libxml.constants.php */
+ $libxmlStatic = phpQuery::$debug === 2
+ ? LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET
+ : LIBXML_DTDLOAD | LIBXML_DTDATTR | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR;
+ $return = $this->document->loadXML($markup, $libxmlStatic);
+ // if (! $return)
+ // $return = $this->document->loadHTML($markup);
+ }
+ if ($return)
+ $this->root = $this->document;
+ }
+ if ($return) {
+ if (!$this->contentType) {
+ if ($this->isXHTML)
+ $this->contentType = 'application/xhtml+xml';
+ else
+ $this->contentType = 'text/xml';
+ }
+ return $return;
+ } else {
+ throw new Exception("Error loading XML markup");
+ }
+ }
+ protected function isXHTML($markup = null)
+ {
+ if (!isset($markup)) {
+ return strpos($this->contentType, 'xhtml') !== false;
+ }
+ // XXX ok ?
+ return strpos($markup, "doctype) && is_object($dom->doctype)
+ // ? $dom->doctype->publicId
+ // : self::$defaultDoctype;
+ }
+ protected function isXML($markup)
+ {
+ // return strpos($markup, ']+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
+ $markup,
+ $matches
+ );
+ if (!isset($matches[0]))
+ return array(null, null);
+ // get attr 'content'
+ preg_match('@content\\s*=\\s*(["|\'])(.+?)\\1@', $matches[0], $matches);
+ if (!isset($matches[0]))
+ return array(null, null);
+ return $this->contentTypeToArray($matches[2]);
+ }
+ protected function charsetFromHTML($markup)
+ {
+ $contentType = $this->contentTypeFromHTML($markup);
+ return $contentType[1];
+ }
+ protected function charsetFromXML($markup)
+ {
+ $matches;
+ // find declaration
+ preg_match(
+ '@<' . '?xml[^>]+encoding\\s*=\\s*(["|\'])(.*?)\\1@i',
+ $markup,
+ $matches
+ );
+ return isset($matches[2])
+ ? strtolower($matches[2])
+ : null;
+ }
+ /**
+ * Repositions meta[type=charset] at the start of head. Bypasses DOMDocument bug.
+ *
+ * @link http://code.google.com/p/phpquery/issues/detail?id=80
+ * @param $html
+ */
+ protected function charsetFixHTML($markup)
+ {
+ $matches = array();
+ // find meta tag
+ preg_match(
+ '@\s* ]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i',
+ $markup,
+ $matches,
+ PREG_OFFSET_CAPTURE
+ );
+ if (!isset($matches[0]))
+ return;
+ $metaContentType = $matches[0][0];
+ $markup = substr($markup, 0, $matches[0][1])
+ . substr($markup, $matches[0][1] + strlen($metaContentType));
+ $headStart = stripos($markup, '');
+ $markup = substr($markup, 0, $headStart + 6) . $metaContentType
+ . substr($markup, $headStart + 6);
+ return $markup;
+ }
+ protected function charsetAppendToHTML($html, $charset, $xhtml = false)
+ {
+ // remove existing meta[type=content-type]
+ $html = preg_replace('@\s* ]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i', '', $html);
+ $meta = ' ';
+ if (strpos($html, ')@s',
+ "{$meta}",
+ $html
+ );
+ }
+ } else {
+ return preg_replace(
+ '@)@s',
+ '' . $meta,
+ $html
+ );
+ }
+ }
+ protected function charsetAppendToXML($markup, $charset)
+ {
+ $declaration = '<' . '?xml version="1.0" encoding="' . $charset . '"?' . '>';
+ return $declaration . $markup;
+ }
+ public static function isDocumentFragmentHTML($markup)
+ {
+ return stripos($markup, 'documentFragmentCreate($node, $sourceCharset);
+ // if ($fake === false)
+ // throw new Exception("Error loading documentFragment markup");
+ // else
+ // $return = array_merge($return,
+ // $this->import($fake->root->childNodes)
+ // );
+ // } else {
+ // $return[] = $this->document->importNode($node, true);
+ // }
+ // }
+ // return $return;
+ // } else {
+ // // string markup
+ // $fake = $this->documentFragmentCreate($source, $sourceCharset);
+ // if ($fake === false)
+ // throw new Exception("Error loading documentFragment markup");
+ // else
+ // return $this->import($fake->root->childNodes);
+ // }
+ if (is_array($source) || $source instanceof DOMNODELIST) {
+ // dom nodes
+ self::debug('Importing nodes to document');
+ foreach ($source as $node)
+ $return[] = $this->document->importNode($node, true);
+ } else {
+ // string markup
+ $fake = $this->documentFragmentCreate($source, $sourceCharset);
+ if ($fake === false)
+ throw new Exception("Error loading documentFragment markup");
+ else
+ return $this->import($fake->root->childNodes);
+ }
+ return $return;
+ }
+ /**
+ * Creates new document fragment.
+ *
+ * @param $source
+ * @return DOMDocumentWrapper
+ */
+ protected function documentFragmentCreate($source, $charset = null)
+ {
+ $fake = new DOMDocumentWrapper();
+ $fake->contentType = $this->contentType;
+ $fake->isXML = $this->isXML;
+ $fake->isHTML = $this->isHTML;
+ $fake->isXHTML = $this->isXHTML;
+ $fake->root = $fake->document;
+ if (!$charset)
+ $charset = $this->charset;
+ // $fake->documentCreate($this->charset);
+ if ($source instanceof DOMNODE && !($source instanceof DOMNODELIST))
+ $source = array($source);
+ if (is_array($source) || $source instanceof DOMNODELIST) {
+ // dom nodes
+ // load fake document
+ if (!$this->documentFragmentLoadMarkup($fake, $charset))
+ return false;
+ $nodes = $fake->import($source);
+ foreach ($nodes as $node)
+ $fake->root->appendChild($node);
+ } else {
+ // string markup
+ $this->documentFragmentLoadMarkup($fake, $charset, $source);
+ }
+ return $fake;
+ }
+ /**
+ *
+ * @param $document DOMDocumentWrapper
+ * @param $markup
+ * @return $document
+ */
+ private function documentFragmentLoadMarkup($fragment, $charset, $markup = null)
+ {
+ // TODO error handling
+ // TODO copy doctype
+ // tempolary turn off
+ $fragment->isDocumentFragment = false;
+ if ($fragment->isXML) {
+ if ($fragment->isXHTML) {
+ // add FAKE element to set default namespace
+ $fragment->loadMarkupXML(''
+ . ''
+ . '' . $markup . ' ');
+ $fragment->root = $fragment->document->firstChild->nextSibling;
+ } else {
+ $fragment->loadMarkupXML('' . $markup . ' ');
+ $fragment->root = $fragment->document->firstChild;
+ }
+ } else {
+ $markup2 = phpQuery::$defaultDoctype . ' ';
+ if ($markup == null) {
+ $markup = "";
+ }
+ $noBody = strpos($markup, 'loadMarkupHTML($markup2);
+ // TODO resolv body tag merging issue
+ $fragment->root = $noBody
+ ? $fragment->document->firstChild->nextSibling->firstChild->nextSibling
+ : $fragment->document->firstChild->nextSibling->firstChild->nextSibling;
+ }
+ if (!$fragment->root)
+ return false;
+ $fragment->isDocumentFragment = true;
+ return true;
+ }
+ protected function documentFragmentToMarkup($fragment)
+ {
+ phpQuery::debug('documentFragmentToMarkup');
+ $tmp = $fragment->isDocumentFragment;
+ $fragment->isDocumentFragment = false;
+ $markup = $fragment->markup();
+ if ($fragment->isXML) {
+ $markup = substr($markup, 0, strrpos($markup, ''));
+ if ($fragment->isXHTML) {
+ $markup = substr($markup, strpos($markup, '') + 6);
+ }
+ } else {
+ $markup = substr($markup, strpos($markup, '') + 6);
+ $markup = substr($markup, 0, strrpos($markup, ''));
+ }
+ $fragment->isDocumentFragment = $tmp;
+ if (phpQuery::$debug)
+ phpQuery::debug('documentFragmentToMarkup: ' . substr($markup, 0, 150));
+ return $markup;
+ }
+ /**
+ * Return document markup, starting with optional $nodes as root.
+ *
+ * @param $nodes DOMNode|DOMNodeList
+ * @return string
+ */
+ public function markup($nodes = null, $innerMarkup = false)
+ {
+ if (isset($nodes) && count($nodes) == 1 && $nodes[0] instanceof DOMDOCUMENT)
+ $nodes = null;
+ if (isset($nodes)) {
+ $markup = '';
+ if (!is_array($nodes) && !($nodes instanceof DOMNODELIST))
+ $nodes = array($nodes);
+ if ($this->isDocumentFragment && !$innerMarkup)
+ foreach ($nodes as $i => $node)
+ if ($node->isSameNode($this->root)) {
+ // var_dump($node);
+ $nodes = array_slice($nodes, 0, $i)
+ + phpQuery::DOMNodeListToArray($node->childNodes)
+ + array_slice($nodes, $i + 1);
+ }
+ if ($this->isXML && !$innerMarkup) {
+ self::debug("Getting outerXML with charset '{$this->charset}'");
+ // we need outerXML, so we can benefit from
+ // $node param support in saveXML()
+ foreach ($nodes as $node)
+ $markup .= $this->document->saveXML($node);
+ } else {
+ $loop = array();
+ if ($innerMarkup)
+ foreach ($nodes as $node) {
+ if ($node->childNodes)
+ foreach ($node->childNodes as $child)
+ $loop[] = $child;
+ else
+ $loop[] = $node;
+ }
+ else
+ $loop = $nodes;
+ self::debug("Getting markup, moving selected nodes (" . count($loop) . ") to new DocumentFragment");
+ $fake = $this->documentFragmentCreate($loop);
+ $markup = $this->documentFragmentToMarkup($fake);
+ }
+ if ($this->isXHTML) {
+ self::debug("Fixing XHTML");
+ $markup = self::markupFixXHTML($markup);
+ }
+ self::debug("Markup: " . substr($markup, 0, 250));
+ return $markup;
+ } else {
+ if ($this->isDocumentFragment) {
+ // documentFragment, html only...
+ self::debug("Getting markup, DocumentFragment detected");
+ // return $this->markup(
+ //// $this->document->getElementsByTagName('body')->item(0)
+ // $this->document->root, true
+ // );
+ $markup = $this->documentFragmentToMarkup($this);
+ // no need for markupFixXHTML, as it's done thought markup($nodes) method
+ return $markup;
+ } else {
+ self::debug("Getting markup (" . ($this->isXML ? 'XML' : 'HTML') . "), final with charset '{$this->charset}'");
+ $markup = $this->isXML
+ ? $this->document->saveXML()
+ : $this->document->saveHTML();
+ if ($this->isXHTML) {
+ self::debug("Fixing XHTML");
+ $markup = self::markupFixXHTML($markup);
+ }
+ self::debug("Markup: " . substr($markup, 0, 250));
+ return $markup;
+ }
+ }
+ }
+ protected static function markupFixXHTML($markup)
+ {
+ $markup = self::expandEmptyTag('script', $markup);
+ $markup = self::expandEmptyTag('select', $markup);
+ $markup = self::expandEmptyTag('textarea', $markup);
+ return $markup;
+ }
+ public static function debug($text)
+ {
+ phpQuery::debug($text);
+ }
+ /**
+ * expandEmptyTag
+ *
+ * @param $tag
+ * @param $xml
+ * @return unknown_type
+ * @author mjaque at ilkebenson dot com
+ * @link http://php.net/manual/en/domdocument.savehtml.php#81256
+ */
+ public static function expandEmptyTag($tag, $xml)
+ {
+ $indice = 0;
+ while ($indice < strlen($xml)) {
+ $pos = strpos($xml, "<$tag ", $indice);
+ if ($pos) {
+ $posCierre = strpos($xml, ">", $pos);
+ if ($xml[$posCierre - 1] == "/") {
+ $xml = substr_replace($xml, ">$tag>", $posCierre - 1, 2);
+ }
+ $indice = $posCierre;
+ } else break;
+ }
+ return $xml;
+ }
+}
+
+/**
+ * Event handling class.
+ *
+ * @author Tobiasz Cudnik
+ * @package phpQuery
+ * @static
+ */
+abstract class phpQueryEvents
+{
+ /**
+ * Trigger a type of event on every matched element.
+ *
+ * @param DOMNode|phpQueryObject|string $document
+ * @param unknown_type $type
+ * @param unknown_type $data
+ *
+ * @TODO exclusive events (with !)
+ * @TODO global events (test)
+ * @TODO support more than event in $type (space-separated)
+ */
+ public static function trigger($document, $type, $data = array(), $node = null)
+ {
+ // trigger: function(type, data, elem, donative, extra) {
+ $documentID = phpQuery::getDocumentID($document);
+ $namespace = null;
+ if (strpos($type, '.') !== false)
+ list($name, $namespace) = explode('.', $type);
+ else
+ $name = $type;
+ if (!$node) {
+ if (self::issetGlobal($documentID, $type)) {
+ $pq = phpQuery::getDocument($documentID);
+ // TODO check add($pq->document)
+ $pq->find('*')->add($pq->document)
+ ->trigger($type, $data);
+ }
+ } else {
+ if (isset($data[0]) && $data[0] instanceof DOMEvent) {
+ $event = $data[0];
+ $event->relatedTarget = $event->target;
+ $event->target = $node;
+ $data = array_slice($data, 1);
+ } else {
+ $event = new DOMEvent(array(
+ 'type' => $type,
+ 'target' => $node,
+ 'timeStamp' => time(),
+ ));
+ }
+ $i = 0;
+ while ($node) {
+ // TODO whois
+ phpQuery::debug("Triggering " . ($i ? "bubbled " : '') . "event '{$type}' on "
+ . "node \n"); //.phpQueryObject::whois($node)."\n");
+ $event->currentTarget = $node;
+ $eventNode = self::getNode($documentID, $node);
+ if (isset($eventNode->eventHandlers)) {
+ foreach ($eventNode->eventHandlers as $eventType => $handlers) {
+ $eventNamespace = null;
+ if (strpos($type, '.') !== false)
+ list($eventName, $eventNamespace) = explode('.', $eventType);
+ else
+ $eventName = $eventType;
+ if ($name != $eventName)
+ continue;
+ if ($namespace && $eventNamespace && $namespace != $eventNamespace)
+ continue;
+ foreach ($handlers as $handler) {
+ phpQuery::debug("Calling event handler\n");
+ $event->data = $handler['data']
+ ? $handler['data']
+ : null;
+ $params = array_merge(array($event), $data);
+ $return = phpQuery::callbackRun($handler['callback'], $params);
+ if ($return === false) {
+ $event->bubbles = false;
+ }
+ }
+ }
+ }
+ // to bubble or not to bubble...
+ if (!$event->bubbles)
+ break;
+ $node = $node->parentNode;
+ $i++;
+ }
+ }
+ }
+ /**
+ * Binds a handler to one or more events (like click) for each matched element.
+ * Can also bind custom events.
+ *
+ * @param DOMNode|phpQueryObject|string $document
+ * @param unknown_type $type
+ * @param unknown_type $data Optional
+ * @param unknown_type $callback
+ *
+ * @TODO support '!' (exclusive) events
+ * @TODO support more than event in $type (space-separated)
+ * @TODO support binding to global events
+ */
+ public static function add($document, $node, $type, $data, $callback = null)
+ {
+ phpQuery::debug("Binding '$type' event");
+ $documentID = phpQuery::getDocumentID($document);
+ // if (is_null($callback) && is_callable($data)) {
+ // $callback = $data;
+ // $data = null;
+ // }
+ $eventNode = self::getNode($documentID, $node);
+ if (!$eventNode)
+ $eventNode = self::setNode($documentID, $node);
+ if (!isset($eventNode->eventHandlers[$type]))
+ $eventNode->eventHandlers[$type] = array();
+ $eventNode->eventHandlers[$type][] = array(
+ 'callback' => $callback,
+ 'data' => $data,
+ );
+ }
+ /**
+ * Enter description here...
+ *
+ * @param DOMNode|phpQueryObject|string $document
+ * @param unknown_type $type
+ * @param unknown_type $callback
+ *
+ * @TODO namespace events
+ * @TODO support more than event in $type (space-separated)
+ */
+ public static function remove($document, $node, $type = null, $callback = null)
+ {
+ $documentID = phpQuery::getDocumentID($document);
+ $eventNode = self::getNode($documentID, $node);
+ if (is_object($eventNode) && isset($eventNode->eventHandlers[$type])) {
+ if ($callback) {
+ foreach ($eventNode->eventHandlers[$type] as $k => $handler)
+ if ($handler['callback'] == $callback)
+ unset($eventNode->eventHandlers[$type][$k]);
+ } else {
+ unset($eventNode->eventHandlers[$type]);
+ }
+ }
+ }
+ protected static function getNode($documentID, $node)
+ {
+ foreach (phpQuery::$documents[$documentID]->eventsNodes as $eventNode) {
+ if ($node->isSameNode($eventNode))
+ return $eventNode;
+ }
+ }
+ protected static function setNode($documentID, $node)
+ {
+ phpQuery::$documents[$documentID]->eventsNodes[] = $node;
+ return phpQuery::$documents[$documentID]->eventsNodes[count(phpQuery::$documents[$documentID]->eventsNodes) - 1];
+ }
+ protected static function issetGlobal($documentID, $type)
+ {
+ return isset(phpQuery::$documents[$documentID])
+ ? in_array($type, phpQuery::$documents[$documentID]->eventsGlobal)
+ : false;
+ }
+}
+
+
+interface ICallbackNamed
+{
+ function hasName();
+ function getName();
+}
+/**
+ * Callback class introduces currying-like pattern.
+ *
+ * Example:
+ * function foo($param1, $param2, $param3) {
+ * var_dump($param1, $param2, $param3);
+ * }
+ * $fooCurried = new Callback('foo',
+ * 'param1 is now statically set',
+ * new CallbackParam, new CallbackParam
+ * );
+ * phpQuery::callbackRun($fooCurried,
+ * array('param2 value', 'param3 value'
+ * );
+ *
+ * Callback class is supported in all phpQuery methods which accepts callbacks.
+ *
+ * @link http://code.google.com/p/phpquery/wiki/Callbacks#Param_Structures
+ * @author Tobiasz Cudnik
+ *
+ * @TODO??? return fake forwarding function created via create_function
+ * @TODO honor paramStructure
+ */
+class Callback
+implements ICallbackNamed
+{
+ public $callback = null;
+ public $params = null;
+ protected $name;
+ public function __construct(
+ $callback,
+ $param1 = null,
+ $param2 = null,
+ $param3 = null
+ ) {
+ $params = func_get_args();
+ $params = array_slice($params, 1);
+ if ($callback instanceof Callback) {
+ // TODO implement recurention
+ } else {
+ $this->callback = $callback;
+ $this->params = $params;
+ }
+ }
+ public function getName()
+ {
+ return 'Callback: ' . $this->name;
+ }
+ public function hasName()
+ {
+ return isset($this->name) && $this->name;
+ }
+ public function setName($name)
+ {
+ $this->name = $name;
+ return $this;
+ }
+ // TODO test me
+ // public function addParams() {
+ // $params = func_get_args();
+ // return new Callback($this->callback, $this->params+$params);
+ // }
+}
+/**
+ * Shorthand for new Callback(create_function(...), ...);
+ *
+ * @author Tobiasz Cudnik
+ */
+class CallbackBody extends Callback
+{
+ public function __construct(
+ $paramList,
+ $code,
+ $param1 = null,
+ $param2 = null,
+ $param3 = null
+ ) {
+ $params = func_get_args();
+ $params = array_slice($params, 2);
+ $this->callback = create_function($paramList, $code);
+ $this->params = $params;
+ }
+}
+/**
+ * Callback type which on execution returns reference passed during creation.
+ *
+ * @author Tobiasz Cudnik
+ */
+class CallbackReturnReference extends Callback
+implements ICallbackNamed
+{
+ protected $reference;
+ public function __construct(&$reference, $name = null)
+ {
+ $this->reference = &$reference;
+ $this->callback = array($this, 'callback');
+ }
+ public function callback()
+ {
+ return $this->reference;
+ }
+ public function getName()
+ {
+ return 'Callback: ' . $this->name;
+ }
+ public function hasName()
+ {
+ return isset($this->name) && $this->name;
+ }
+}
+/**
+ * Callback type which on execution returns value passed during creation.
+ *
+ * @author Tobiasz Cudnik
+ */
+class CallbackReturnValue extends Callback
+implements ICallbackNamed
+{
+ protected $value;
+ protected $name;
+ public function __construct($value, $name = null)
+ {
+ $this->value = &$value;
+ $this->name = $name;
+ $this->callback = array($this, 'callback');
+ }
+ public function callback()
+ {
+ return $this->value;
+ }
+ public function __toString()
+ {
+ return $this->getName();
+ }
+ public function getName()
+ {
+ return 'Callback: ' . $this->name;
+ }
+ public function hasName()
+ {
+ return isset($this->name) && $this->name;
+ }
+}
+/**
+ * CallbackParameterToReference can be used when we don't really want a callback,
+ * only parameter passed to it. CallbackParameterToReference takes first
+ * parameter's value and passes it to reference.
+ *
+ * @author Tobiasz Cudnik
+ */
+class CallbackParameterToReference extends Callback
+{
+ /**
+ * @param $reference
+ * @TODO implement $paramIndex;
+ * param index choose which callback param will be passed to reference
+ */
+ public function __construct(&$reference)
+ {
+ $this->callback = &$reference;
+ }
+}
+//class CallbackReference extends Callback {
+// /**
+// *
+// * @param $reference
+// * @param $paramIndex
+// * @todo implement $paramIndex; param index choose which callback param will be passed to reference
+// */
+// public function __construct(&$reference, $name = null){
+// $this->callback =& $reference;
+// }
+//}
+class CallbackParam
+{
+}
+
+/**
+ * Class representing phpQuery objects.
+ *
+ * @author Tobiasz Cudnik
+ * @package phpQuery
+ * @method phpQueryObject clone() clone()
+ * @method phpQueryObject empty() empty()
+ * @method phpQueryObject next() next($selector = null)
+ * @method phpQueryObject prev() prev($selector = null)
+ * @property Int $length
+ */
+class phpQueryObject
+implements Iterator, Countable, ArrayAccess
+{
+ public $documentID = null;
+ /**
+ * DOMDocument class.
+ *
+ * @var DOMDocument
+ */
+ public $document = null;
+ public $charset = null;
+ /**
+ *
+ * @var DOMDocumentWrapper
+ */
+ public $documentWrapper = null;
+ /**
+ * XPath interface.
+ *
+ * @var DOMXPath
+ */
+ public $xpath = null;
+ /**
+ * Stack of selected elements.
+ * @TODO refactor to ->nodes
+ * @var array
+ */
+ public $elements = array();
+ /**
+ * @access private
+ */
+ protected $elementsBackup = array();
+ /**
+ * @access private
+ */
+ protected $previous = null;
+ /**
+ * @access private
+ * @TODO deprecate
+ */
+ protected $root = array();
+ /**
+ * Indicated if doument is just a fragment (no tag).
+ *
+ * Every document is realy a full document, so even documentFragments can
+ * be queried against , but getDocument(id)->htmlOuter() will return
+ * only contents of .
+ *
+ * @var bool
+ */
+ public $documentFragment = true;
+ /**
+ * Iterator interface helper
+ * @access private
+ */
+ protected $elementsInterator = array();
+ /**
+ * Iterator interface helper
+ * @access private
+ */
+ protected $valid = false;
+ /**
+ * Iterator interface helper
+ * @access private
+ */
+ protected $current = null;
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function __construct($documentID)
+ {
+ // if ($documentID instanceof self)
+ // var_dump($documentID->getDocumentID());
+ $id = $documentID instanceof self
+ ? $documentID->getDocumentID()
+ : $documentID;
+ // var_dump($id);
+ if (!isset(phpQuery::$documents[$id])) {
+ // var_dump(phpQuery::$documents);
+ throw new Exception("Document with ID '{$id}' isn't loaded. Use phpQuery::newDocument(\$html) or phpQuery::newDocumentFile(\$file) first.");
+ }
+ $this->documentID = $id;
+ $this->documentWrapper = &phpQuery::$documents[$id];
+ $this->document = &$this->documentWrapper->document;
+ $this->xpath = &$this->documentWrapper->xpath;
+ $this->charset = &$this->documentWrapper->charset;
+ $this->documentFragment = &$this->documentWrapper->isDocumentFragment;
+ // TODO check $this->DOM->documentElement;
+ // $this->root = $this->document->documentElement;
+ $this->root = &$this->documentWrapper->root;
+ // $this->toRoot();
+ $this->elements = array($this->root);
+ }
+ /**
+ *
+ * @access private
+ * @param $attr
+ * @return unknown_type
+ */
+ public function __get($attr)
+ {
+ switch ($attr) {
+ // FIXME doesnt work at all ?
+ case 'length':
+ return $this->size();
+ break;
+ default:
+ return $this->$attr;
+ }
+ }
+ /**
+ * Saves actual object to $var by reference.
+ * Useful when need to break chain.
+ * @param phpQueryObject $var
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function toReference(&$var)
+ {
+ return $var = $this;
+ }
+ public function documentFragment($state = null)
+ {
+ if ($state) {
+ phpQuery::$documents[$this->getDocumentID()]['documentFragment'] = $state;
+ return $this;
+ }
+ return $this->documentFragment;
+ }
+ /**
+ * @access private
+ * @TODO documentWrapper
+ */
+ protected function isRoot($node)
+ {
+ // return $node instanceof DOMDOCUMENT || $node->tagName == 'html';
+ return $node instanceof DOMDOCUMENT
+ || ($node instanceof DOMELEMENT && $node->tagName == 'html')
+ || $this->root->isSameNode($node);
+ }
+ /**
+ * @access private
+ */
+ protected function stackIsRoot()
+ {
+ return $this->size() == 1 && $this->isRoot($this->elements[0]);
+ }
+ /**
+ * Enter description here...
+ * NON JQUERY METHOD
+ *
+ * Watch out, it doesn't creates new instance, can be reverted with end().
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function toRoot()
+ {
+ $this->elements = array($this->root);
+ return $this;
+ // return $this->newInstance(array($this->root));
+ }
+ /**
+ * Saves object's DocumentID to $var by reference.
+ *
+ * $myDocumentId;
+ * phpQuery::newDocument('
')
+ * ->getDocumentIDRef($myDocumentId)
+ * ->find('div')->...
+ *
+ *
+ * @param unknown_type $domId
+ * @see phpQuery::newDocument
+ * @see phpQuery::newDocumentFile
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function getDocumentIDRef(&$documentID)
+ {
+ $documentID = $this->getDocumentID();
+ return $this;
+ }
+ /**
+ * Returns object with stack set to document root.
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function getDocument()
+ {
+ return phpQuery::getDocument($this->getDocumentID());
+ }
+ /**
+ *
+ * @return DOMDocument
+ */
+ public function getDOMDocument()
+ {
+ return $this->document;
+ }
+ /**
+ * Get object's Document ID.
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function getDocumentID()
+ {
+ return $this->documentID;
+ }
+ /**
+ * Unloads whole document from memory.
+ * CAUTION! None further operations will be possible on this document.
+ * All objects refering to it will be useless.
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function unloadDocument()
+ {
+ phpQuery::unloadDocuments($this->getDocumentID());
+ }
+ public function isHTML()
+ {
+ return $this->documentWrapper->isHTML;
+ }
+ public function isXHTML()
+ {
+ return $this->documentWrapper->isXHTML;
+ }
+ public function isXML()
+ {
+ return $this->documentWrapper->isXML;
+ }
+ /**
+ * Enter description here...
+ *
+ * @link http://docs.jquery.com/Ajax/serialize
+ * @return string
+ */
+ public function serialize()
+ {
+ return phpQuery::param($this->serializeArray());
+ }
+ /**
+ * Enter description here...
+ *
+ * @link http://docs.jquery.com/Ajax/serializeArray
+ * @return array
+ */
+ public function serializeArray($submit = null)
+ {
+ $source = $this->filter('form, input, select, textarea')
+ ->find('input, select, textarea')
+ ->andSelf()
+ ->not('form');
+ $return = array();
+ // $source->dumpDie();
+ foreach ($source as $input) {
+ $input = phpQuery::pq($input);
+ if ($input->is('[disabled]'))
+ continue;
+ if (!$input->is('[name]'))
+ continue;
+ if ($input->is('[type=checkbox]') && !$input->is('[checked]'))
+ continue;
+ // jquery diff
+ if ($submit && $input->is('[type=submit]')) {
+ if ($submit instanceof DOMELEMENT && !$input->elements[0]->isSameNode($submit))
+ continue;
+ else if (is_string($submit) && $input->attr('name') != $submit)
+ continue;
+ }
+ $return[] = array(
+ 'name' => $input->attr('name'),
+ 'value' => $input->val(),
+ );
+ }
+ return $return;
+ }
+ /**
+ * @access private
+ */
+ protected function debug($in)
+ {
+ if (!phpQuery::$debug)
+ return;
+ print('');
+ print_r($in);
+ // file debug
+ // file_put_contents(dirname(__FILE__).'/phpQuery.log', print_r($in, true)."\n", FILE_APPEND);
+ // quite handy debug trace
+ // if ( is_array($in))
+ // print_r(array_slice(debug_backtrace(), 3));
+ print(" \n");
+ }
+ /**
+ * @access private
+ */
+ protected function isRegexp($pattern)
+ {
+ return in_array(
+ $pattern[mb_strlen($pattern) - 1],
+ array('^', '*', '$')
+ );
+ }
+ /**
+ * Determines if $char is really a char.
+ *
+ * @param string $char
+ * @return bool
+ * @todo rewrite me to charcode range ! ;)
+ * @access private
+ */
+ protected function isChar($char)
+ {
+ return extension_loaded('mbstring') && phpQuery::$mbstringSupport
+ ? mb_eregi('\w', $char)
+ : preg_match('@\w@', $char);
+ }
+ /**
+ * @access private
+ */
+ protected function parseSelector($query)
+ {
+ // clean spaces
+ // TODO include this inside parsing ?
+ $query = trim(
+ preg_replace(
+ '@\s+@',
+ ' ',
+ preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query)
+ )
+ );
+ $queries = array(array());
+ if (!$query)
+ return $queries;
+ $return = &$queries[0];
+ $specialChars = array('>', ' ');
+ // $specialCharsMapping = array('/' => '>');
+ $specialCharsMapping = array();
+ $strlen = mb_strlen($query);
+ $classChars = array('.', '-');
+ $pseudoChars = array('-');
+ $tagChars = array('*', '|', '-');
+ // split multibyte string
+ // http://code.google.com/p/phpquery/issues/detail?id=76
+ $_query = array();
+ for ($i = 0; $i < $strlen; $i++)
+ $_query[] = mb_substr($query, $i, 1);
+ $query = $_query;
+ // it works, but i dont like it...
+ $i = 0;
+ while ($i < $strlen) {
+ $c = $query[$i];
+ $tmp = '';
+ // TAG
+ if ($this->isChar($c) || in_array($c, $tagChars)) {
+ while (
+ isset($query[$i])
+ && ($this->isChar($query[$i]) || in_array($query[$i], $tagChars))
+ ) {
+ $tmp .= $query[$i];
+ $i++;
+ }
+ $return[] = $tmp;
+ // IDs
+ } else if ($c == '#') {
+ $i++;
+ while (isset($query[$i]) && ($this->isChar($query[$i]) || $query[$i] == '-')) {
+ $tmp .= $query[$i];
+ $i++;
+ }
+ $return[] = '#' . $tmp;
+ // SPECIAL CHARS
+ } else if (in_array($c, $specialChars)) {
+ $return[] = $c;
+ $i++;
+ // MAPPED SPECIAL MULTICHARS
+ // } else if ( $c.$query[$i+1] == '//') {
+ // $return[] = ' ';
+ // $i = $i+2;
+ // MAPPED SPECIAL CHARS
+ } else if (isset($specialCharsMapping[$c])) {
+ $return[] = $specialCharsMapping[$c];
+ $i++;
+ // COMMA
+ } else if ($c == ',') {
+ $queries[] = array();
+ $return = &$queries[count($queries) - 1];
+ $i++;
+ while (isset($query[$i]) && $query[$i] == ' ')
+ $i++;
+ // CLASSES
+ } else if ($c == '.') {
+ while (isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $classChars))) {
+ $tmp .= $query[$i];
+ $i++;
+ }
+ $return[] = $tmp;
+ // ~ General Sibling Selector
+ } else if ($c == '~') {
+ $spaceAllowed = true;
+ $tmp .= $query[$i++];
+ while (
+ isset($query[$i])
+ && ($this->isChar($query[$i])
+ || in_array($query[$i], $classChars)
+ || $query[$i] == '*'
+ || ($query[$i] == ' ' && $spaceAllowed)
+ )
+ ) {
+ if ($query[$i] != ' ')
+ $spaceAllowed = false;
+ $tmp .= $query[$i];
+ $i++;
+ }
+ $return[] = $tmp;
+ // + Adjacent sibling selectors
+ } else if ($c == '+') {
+ $spaceAllowed = true;
+ $tmp .= $query[$i++];
+ while (
+ isset($query[$i])
+ && ($this->isChar($query[$i])
+ || in_array($query[$i], $classChars)
+ || $query[$i] == '*'
+ || ($spaceAllowed && $query[$i] == ' ')
+ )
+ ) {
+ if ($query[$i] != ' ')
+ $spaceAllowed = false;
+ $tmp .= $query[$i];
+ $i++;
+ }
+ $return[] = $tmp;
+ // ATTRS
+ } else if ($c == '[') {
+ $stack = 1;
+ $tmp .= $c;
+ while (isset($query[++$i])) {
+ $tmp .= $query[$i];
+ if ($query[$i] == '[') {
+ $stack++;
+ } else if ($query[$i] == ']') {
+ $stack--;
+ if (!$stack)
+ break;
+ }
+ }
+ $return[] = $tmp;
+ $i++;
+ // PSEUDO CLASSES
+ } else if ($c == ':') {
+ $stack = 1;
+ $tmp .= $query[$i++];
+ while (isset($query[$i]) && ($this->isChar($query[$i]) || in_array($query[$i], $pseudoChars))) {
+ $tmp .= $query[$i];
+ $i++;
+ }
+ // with arguments ?
+ if (isset($query[$i]) && $query[$i] == '(') {
+ $tmp .= $query[$i];
+ $stack = 1;
+ while (isset($query[++$i])) {
+ $tmp .= $query[$i];
+ if ($query[$i] == '(') {
+ $stack++;
+ } else if ($query[$i] == ')') {
+ $stack--;
+ if (!$stack)
+ break;
+ }
+ }
+ $return[] = $tmp;
+ $i++;
+ } else {
+ $return[] = $tmp;
+ }
+ } else {
+ $i++;
+ }
+ }
+ foreach ($queries as $k => $q) {
+ if (isset($q[0])) {
+ if (isset($q[0][0]) && $q[0][0] == ':')
+ array_unshift($queries[$k], '*');
+ if ($q[0] != '>')
+ array_unshift($queries[$k], ' ');
+ }
+ }
+ return $queries;
+ }
+ /**
+ * Return matched DOM nodes.
+ *
+ * @param int $index
+ * @return array|DOMElement Single DOMElement or array of DOMElement.
+ */
+ public function get($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ $return = isset($index)
+ ? (isset($this->elements[$index]) ? $this->elements[$index] : null)
+ : $this->elements;
+ // pass thou callbacks
+ $args = func_get_args();
+ $args = array_slice($args, 1);
+ foreach ($args as $callback) {
+ if (is_array($return))
+ foreach ($return as $k => $v)
+ $return[$k] = phpQuery::callbackRun($callback, array($v));
+ else
+ $return = phpQuery::callbackRun($callback, array($return));
+ }
+ return $return;
+ }
+ /**
+ * Return matched DOM nodes.
+ * jQuery difference.
+ *
+ * @param int $index
+ * @return array|string Returns string if $index != null
+ * @todo implement callbacks
+ * @todo return only arrays ?
+ * @todo maybe other name...
+ */
+ public function getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ if (!is_null($index) && is_int($index))
+ $return = $this->eq($index)->text();
+ else {
+ $return = array();
+ for ($i = 0; $i < $this->size(); $i++) {
+ $return[] = $this->eq($i)->text();
+ }
+ }
+ // pass thou callbacks
+ $args = func_get_args();
+ $args = array_slice($args, 1);
+ foreach ($args as $callback) {
+ $return = phpQuery::callbackRun($callback, array($return));
+ }
+ return $return;
+ }
+ /**
+ * Return matched DOM nodes.
+ * jQuery difference.
+ *
+ * @param int $index
+ * @return array|string Returns string if $index != null
+ * @todo implement callbacks
+ * @todo return only arrays ?
+ * @todo maybe other name...
+ */
+ public function getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ if (!is_null($index) && is_int($index))
+ $return = $this->eq($index)->text();
+ else {
+ $return = array();
+ for ($i = 0; $i < $this->size(); $i++) {
+ $return[] = $this->eq($i)->text();
+ }
+ // pass thou callbacks
+ $args = func_get_args();
+ $args = array_slice($args, 1);
+ }
+ foreach ($args as $callback) {
+ if (is_array($return))
+ foreach ($return as $k => $v)
+ $return[$k] = phpQuery::callbackRun($callback, array($v));
+ else
+ $return = phpQuery::callbackRun($callback, array($return));
+ }
+ return $return;
+ }
+ /**
+ * Returns new instance of actual class.
+ *
+ * @param array $newStack Optional. Will replace old stack with new and move old one to history.c
+ */
+ public function newInstance($newStack = null)
+ {
+ $class = get_class($this);
+ // support inheritance by passing old object to overloaded constructor
+ $new = $class != 'phpQuery'
+ ? new $class($this, $this->getDocumentID())
+ : new phpQueryObject($this->getDocumentID());
+ $new->previous = $this;
+ if (is_null($newStack)) {
+ $new->elements = $this->elements;
+ if ($this->elementsBackup)
+ $this->elements = $this->elementsBackup;
+ } else if (is_string($newStack)) {
+ $new->elements = phpQuery::pq($newStack, $this->getDocumentID())->stack();
+ } else {
+ $new->elements = $newStack;
+ }
+ return $new;
+ }
+ /**
+ * Enter description here...
+ *
+ * In the future, when PHP will support XLS 2.0, then we would do that this way:
+ * contains(tokenize(@class, '\s'), "something")
+ * @param unknown_type $class
+ * @param unknown_type $node
+ * @return boolean
+ * @access private
+ */
+ protected function matchClasses($class, $node)
+ {
+ // multi-class
+ if (mb_strpos($class, '.', 1)) {
+ $classes = explode('.', substr($class, 1));
+ $classesCount = count($classes);
+ $nodeClasses = explode(' ', $node->getAttribute('class'));
+ $nodeClassesCount = count($nodeClasses);
+ if ($classesCount > $nodeClassesCount)
+ return false;
+ $diff = count(
+ array_diff(
+ $classes,
+ $nodeClasses
+ )
+ );
+ if (!$diff)
+ return true;
+ // single-class
+ } else {
+ return in_array(
+ // strip leading dot from class name
+ substr($class, 1),
+ // get classes for element as array
+ explode(' ', $node->getAttribute('class'))
+ );
+ }
+ }
+ /**
+ * @access private
+ */
+ protected function runQuery($XQuery, $selector = null, $compare = null)
+ {
+ if ($compare && !method_exists($this, $compare))
+ return false;
+ $stack = array();
+ if (!$this->elements)
+ $this->debug('Stack empty, skipping...');
+ // var_dump($this->elements[0]->nodeType);
+ // element, document
+ foreach ($this->stack(array(1, 9, 13)) as $k => $stackNode) {
+ $detachAfter = false;
+ // to work on detached nodes we need temporary place them somewhere
+ // thats because context xpath queries sucks ;]
+ $testNode = $stackNode;
+ while ($testNode) {
+ if (!$testNode->parentNode && !$this->isRoot($testNode)) {
+ $this->root->appendChild($testNode);
+ $detachAfter = $testNode;
+ break;
+ }
+ $testNode = isset($testNode->parentNode)
+ ? $testNode->parentNode
+ : null;
+ }
+ // XXX tmp ?
+ $xpath = $this->documentWrapper->isXHTML
+ ? $this->getNodeXpath($stackNode, 'html')
+ : $this->getNodeXpath($stackNode);
+ // FIXME pseudoclasses-only query, support XML
+ $query = $XQuery == '//' && $xpath == '/html[1]'
+ ? '//*'
+ : $xpath . $XQuery;
+ $this->debug("XPATH: {$query}");
+ // run query, get elements
+ $nodes = $this->xpath->query($query);
+ $this->debug("QUERY FETCHED");
+ if (!$nodes->length)
+ $this->debug('Nothing found');
+ $debug = array();
+ foreach ($nodes as $node) {
+ $matched = false;
+ if ($compare) {
+ phpQuery::$debug ?
+ $this->debug("Found: " . $this->whois($node) . ", comparing with {$compare}()")
+ : null;
+ $phpQueryDebug = phpQuery::$debug;
+ phpQuery::$debug = false;
+ // TODO ??? use phpQuery::callbackRun()
+ if (call_user_func_array(array($this, $compare), array($selector, $node)))
+ $matched = true;
+ phpQuery::$debug = $phpQueryDebug;
+ } else {
+ $matched = true;
+ }
+ if ($matched) {
+ if (phpQuery::$debug)
+ $debug[] = $this->whois($node);
+ $stack[] = $node;
+ }
+ }
+ if (phpQuery::$debug) {
+ $this->debug("Matched " . count($debug) . ": " . implode(', ', $debug));
+ }
+ if ($detachAfter)
+ $this->root->removeChild($detachAfter);
+ }
+ $this->elements = $stack;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function find($selectors, $context = null, $noHistory = false)
+ {
+ if (!$noHistory)
+ // backup last stack /for end()/
+ $this->elementsBackup = $this->elements;
+ // allow to define context
+ // TODO combine code below with phpQuery::pq() context guessing code
+ // as generic function
+ if ($context) {
+ if (!is_array($context) && $context instanceof DOMELEMENT)
+ $this->elements = array($context);
+ else if (is_array($context)) {
+ $this->elements = array();
+ foreach ($context as $c)
+ if ($c instanceof DOMELEMENT)
+ $this->elements[] = $c;
+ } else if ($context instanceof self)
+ $this->elements = $context->elements;
+ }
+ $queries = $this->parseSelector($selectors);
+ $this->debug(array('FIND', $selectors, $queries));
+ $XQuery = '';
+ // remember stack state because of multi-queries
+ $oldStack = $this->elements;
+ // here we will be keeping found elements
+ $stack = array();
+ foreach ($queries as $selector) {
+ $this->elements = $oldStack;
+ $delimiterBefore = false;
+ foreach ($selector as $s) {
+ // TAG
+ $isTag = extension_loaded('mbstring') && phpQuery::$mbstringSupport
+ ? mb_ereg_match('^[\w|\||-]+$', $s) || $s == '*'
+ : preg_match('@^[\w|\||-]+$@', $s) || $s == '*';
+ if ($isTag) {
+ if ($this->isXML()) {
+ // namespace support
+ if (mb_strpos($s, '|') !== false) {
+ $ns = $tag = null;
+ list($ns, $tag) = explode('|', $s);
+ $XQuery .= "$ns:$tag";
+ } else if ($s == '*') {
+ $XQuery .= "*";
+ } else {
+ $XQuery .= "*[local-name()='$s']";
+ }
+ } else {
+ $XQuery .= $s;
+ }
+ // ID
+ } else if ($s[0] == '#') {
+ if ($delimiterBefore)
+ $XQuery .= '*';
+ $XQuery .= "[@id='" . substr($s, 1) . "']";
+ // ATTRIBUTES
+ } else if ($s[0] == '[') {
+ if ($delimiterBefore)
+ $XQuery .= '*';
+ // strip side brackets
+ $attr = trim($s, '][');
+ $execute = false;
+ // attr with specifed value
+ if (mb_strpos($s, '=')) {
+ $value = null;
+ list($attr, $value) = explode('=', $attr);
+ $value = trim($value, "'\"");
+ if ($this->isRegexp($attr)) {
+ // cut regexp character
+ $attr = substr($attr, 0, -1);
+ $execute = true;
+ $XQuery .= "[@{$attr}]";
+ } else {
+ $XQuery .= "[@{$attr}='{$value}']";
+ }
+ // attr without specified value
+ } else {
+ $XQuery .= "[@{$attr}]";
+ }
+ if ($execute) {
+ $this->runQuery($XQuery, $s, 'is');
+ $XQuery = '';
+ if (!$this->length())
+ break;
+ }
+ // CLASSES
+ } else if ($s[0] == '.') {
+ // TODO use return $this->find("./self::*[contains(concat(\" \",@class,\" \"), \" $class \")]");
+ // thx wizDom ;)
+ if ($delimiterBefore)
+ $XQuery .= '*';
+ $XQuery .= '[@class]';
+ $this->runQuery($XQuery, $s, 'matchClasses');
+ $XQuery = '';
+ if (!$this->length())
+ break;
+ // ~ General Sibling Selector
+ } else if ($s[0] == '~') {
+ $this->runQuery($XQuery);
+ $XQuery = '';
+ $this->elements = $this
+ ->siblings(
+ substr($s, 1)
+ )->elements;
+ if (!$this->length())
+ break;
+ // + Adjacent sibling selectors
+ } else if ($s[0] == '+') {
+ // TODO /following-sibling::
+ $this->runQuery($XQuery);
+ $XQuery = '';
+ $subSelector = substr($s, 1);
+ $subElements = $this->elements;
+ $this->elements = array();
+ foreach ($subElements as $node) {
+ // search first DOMElement sibling
+ $test = $node->nextSibling;
+ while ($test && !($test instanceof DOMELEMENT))
+ $test = $test->nextSibling;
+ if ($test && $this->is($subSelector, $test))
+ $this->elements[] = $test;
+ }
+ if (!$this->length())
+ break;
+ // PSEUDO CLASSES
+ } else if ($s[0] == ':') {
+ // TODO optimization for :first :last
+ if ($XQuery) {
+ $this->runQuery($XQuery);
+ $XQuery = '';
+ }
+ if (!$this->length())
+ break;
+ $this->pseudoClasses($s);
+ if (!$this->length())
+ break;
+ // DIRECT DESCENDANDS
+ } else if ($s == '>') {
+ $XQuery .= '/';
+ $delimiterBefore = 2;
+ // ALL DESCENDANDS
+ } else if ($s == ' ') {
+ $XQuery .= '//';
+ $delimiterBefore = 2;
+ // ERRORS
+ } else {
+ phpQuery::debug("Unrecognized token '$s'");
+ }
+ $delimiterBefore = $delimiterBefore === 2;
+ }
+ // run query if any
+ if ($XQuery && $XQuery != '//') {
+ $this->runQuery($XQuery);
+ $XQuery = '';
+ }
+ foreach ($this->elements as $node)
+ if (!$this->elementsContainsNode($node, $stack))
+ $stack[] = $node;
+ }
+ $this->elements = $stack;
+ return $this->newInstance();
+ }
+ /**
+ * @todo create API for classes with pseudoselectors
+ * @access private
+ */
+ protected function pseudoClasses($class)
+ {
+ // TODO clean args parsing ?
+ $class = ltrim($class, ':');
+ $haveArgs = mb_strpos($class, '(');
+ if ($haveArgs !== false) {
+ $args = substr($class, $haveArgs + 1, -1);
+ $class = substr($class, 0, $haveArgs);
+ }
+ switch ($class) {
+ case 'even':
+ case 'odd':
+ $stack = array();
+ foreach ($this->elements as $i => $node) {
+ if ($class == 'even' && ($i % 2) == 0)
+ $stack[] = $node;
+ else if ($class == 'odd' && $i % 2)
+ $stack[] = $node;
+ }
+ $this->elements = $stack;
+ break;
+ case 'eq':
+ $k = intval($args);
+ if ($k < 0) {
+ $this->elements = array($this->elements[count($this->elements) + $k]);
+ } else {
+ $this->elements = isset($this->elements[$k])
+ ? array($this->elements[$k])
+ : array();
+ }
+ break;
+ case 'gt':
+ $this->elements = array_slice($this->elements, $args + 1);
+ break;
+ case 'lt':
+ $this->elements = array_slice($this->elements, 0, $args + 1);
+ break;
+ case 'first':
+ if (isset($this->elements[0]))
+ $this->elements = array($this->elements[0]);
+ break;
+ case 'last':
+ if ($this->elements)
+ $this->elements = array($this->elements[count($this->elements) - 1]);
+ break;
+ /*case 'parent':
+ $stack = array();
+ foreach($this->elements as $node) {
+ if ( $node->childNodes->length )
+ $stack[] = $node;
+ }
+ $this->elements = $stack;
+ break;*/
+ case 'contains':
+ $text = trim($args, "\"'");
+ $stack = array();
+ foreach ($this->elements as $node) {
+ if (mb_stripos($node->textContent, $text) === false)
+ continue;
+ $stack[] = $node;
+ }
+ $this->elements = $stack;
+ break;
+ case 'not':
+ $selector = self::unQuote($args);
+ $this->elements = $this->not($selector)->stack();
+ break;
+ case 'slice':
+ // TODO jQuery difference ?
+ $args = explode(
+ ',',
+ str_replace(', ', ',', trim($args, "\"'"))
+ );
+ $start = $args[0];
+ $end = isset($args[1])
+ ? $args[1]
+ : null;
+ if ($end > 0)
+ $end = $end - $start;
+ $this->elements = array_slice($this->elements, $start, $end);
+ break;
+ case 'has':
+ $selector = trim($args, "\"'");
+ $stack = array();
+ foreach ($this->stack(1) as $el) {
+ if ($this->find($selector, $el, true)->length)
+ $stack[] = $el;
+ }
+ $this->elements = $stack;
+ break;
+ case 'submit':
+ case 'reset':
+ $this->elements = phpQuery::merge(
+ $this->map(
+ array($this, 'is'),
+ "input[type=$class]",
+ new CallbackParam()
+ ),
+ $this->map(
+ array($this, 'is'),
+ "button[type=$class]",
+ new CallbackParam()
+ )
+ );
+ break;
+ // $stack = array();
+ // foreach($this->elements as $node)
+ // if ($node->is('input[type=submit]') || $node->is('button[type=submit]'))
+ // $stack[] = $el;
+ // $this->elements = $stack;
+ case 'input':
+ $this->elements = $this->map(
+ array($this, 'is'),
+ 'input',
+ new CallbackParam()
+ )->elements;
+ break;
+ case 'password':
+ case 'checkbox':
+ case 'radio':
+ case 'hidden':
+ case 'image':
+ case 'file':
+ $this->elements = $this->map(
+ array($this, 'is'),
+ "input[type=$class]",
+ new CallbackParam()
+ )->elements;
+ break;
+ case 'parent':
+ $this->elements = $this->map(
+ function ($node) {
+ return $node instanceof DOMELEMENT && $node->childNodes->length
+ ? $node : null;
+ }
+ )->elements;
+ break;
+ case 'empty':
+ $this->elements = $this->map(
+ function ($node) {
+ return $node instanceof DOMELEMENT && $node->childNodes->length
+ ? null : $node;
+ }
+ )->elements;
+ break;
+ case 'disabled':
+ case 'selected':
+ case 'checked':
+ $this->elements = $this->map(
+ array($this, 'is'),
+ "[$class]",
+ new CallbackParam()
+ )->elements;
+ break;
+ case 'enabled':
+ $this->elements = $this->map(
+ function ($node) {
+ return pq($node)->not(":disabled") ? $node : null;
+ }
+ )->elements;
+ break;
+ case 'header':
+ $this->elements = $this->map(
+ function ($node) {
+ $isHeader = isset($node->tagName) && in_array($node->tagName, array(
+ "h1", "h2", "h3", "h4", "h5", "h6", "h7"
+ ));
+ return $isHeader
+ ? $node
+ : null;
+ }
+ )->elements;
+ // $this->elements = $this->map(
+ // create_function('$node', '$node = pq($node);
+ // return $node->is("h1")
+ // || $node->is("h2")
+ // || $node->is("h3")
+ // || $node->is("h4")
+ // || $node->is("h5")
+ // || $node->is("h6")
+ // || $node->is("h7")
+ // ? $node
+ // : null;')
+ // )->elements;
+ break;
+ case 'only-child':
+ $this->elements = $this->map(
+ function ($node) {
+ return pq($node)->siblings()->size() == 0 ? $node : null;
+ }
+ )->elements;
+ break;
+ case 'first-child':
+ $this->elements = $this->map(
+ function ($node) {
+ return pq($node)->prevAll()->size() == 0 ? $node : null;
+ }
+ )->elements;
+ break;
+ case 'last-child':
+ $this->elements = $this->map(
+ function ($node) {
+ return pq($node)->nextAll()->size() == 0 ? $node : null;
+ }
+ )->elements;
+ break;
+ case 'nth-child':
+ $param = trim($args, "\"'");
+ if (!$param)
+ break;
+ // nth-child(n+b) to nth-child(1n+b)
+ if ($param[0] == 'n')
+ $param = '1' . $param;
+ // :nth-child(index/even/odd/equation)
+ if ($param == 'even' || $param == 'odd')
+ $mapped = $this->map(
+ function ($node, $param) {
+ $index = pq($node)->prevAll()->size() + 1;
+ if ($param == "even" && ($index % 2) == 0)
+ return $node;
+ else if ($param == "odd" && $index % 2 == 1)
+ return $node;
+ else
+ return null;
+ },
+ new CallbackParam(),
+ $param
+ );
+ else if (mb_strlen($param) > 1 && preg_match('/^(\d*)n([-+]?)(\d*)/', $param) === 1)
+ // an+b
+ $mapped = $this->map(
+ function ($node, $param) {
+ $prevs = pq($node)->prevAll()->size();
+ $index = 1 + $prevs;
+
+ preg_match("/^(\d*)n([-+]?)(\d*)/", $param, $matches);
+ $a = intval($matches[1]);
+ $b = intval($matches[3]);
+ if ($matches[2] === "-") {
+ $b = -$b;
+ }
+
+ if ($a > 0) {
+ return ($index - $b) % $a == 0
+ ? $node
+ : null;
+ phpQuery::debug($a . "*" . floor($index / $a) . "+$b-1 == " . ($a * floor($index / $a) + $b - 1) . " ?= $prevs");
+ return $a * floor($index / $a) + $b - 1 == $prevs
+ ? $node
+ : null;
+ } else if ($a == 0)
+ return $index == $b
+ ? $node
+ : null;
+ else
+ // negative value
+ return $index <= $b
+ ? $node
+ : null;
+ // if (! $b)
+ // return $index%$a == 0
+ // ? $node
+ // : null;
+ // else
+ // return ($index-$b)%$a == 0
+ // ? $node
+ // : null;
+ },
+ new CallbackParam(),
+ $param
+ );
+ else
+ // index
+ $mapped = $this->map(
+ function ($node, $index) {
+ $prevs = pq($node)->prevAll()->size();
+ if ($prevs && $prevs == $index - 1)
+ return $node;
+ else if (!$prevs && $index == 1)
+ return $node;
+ else
+ return null;
+ },
+ new CallbackParam(),
+ $param
+ );
+ $this->elements = $mapped->elements;
+ break;
+ default:
+ $this->debug("Unknown pseudoclass '{$class}', skipping...");
+ }
+ }
+ /**
+ * @access private
+ */
+ protected function __pseudoClassParam($paramsString)
+ {
+ // TODO;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function is($selector, $nodes = null)
+ {
+ phpQuery::debug(array("Is:", $selector));
+ if (!$selector)
+ return false;
+ $oldStack = $this->elements;
+ $returnArray = false;
+ if ($nodes && is_array($nodes)) {
+ $this->elements = $nodes;
+ } else if ($nodes)
+ $this->elements = array($nodes);
+ $this->filter($selector, true);
+ $stack = $this->elements;
+ $this->elements = $oldStack;
+ if ($nodes)
+ return $stack ? $stack : null;
+ return (bool)count($stack);
+ }
+ /**
+ * Enter description here...
+ * jQuery difference.
+ *
+ * Callback:
+ * - $index int
+ * - $node DOMNode
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @link http://docs.jquery.com/Traversing/filter
+ */
+ public function filterCallback($callback, $_skipHistory = false)
+ {
+ if (!$_skipHistory) {
+ $this->elementsBackup = $this->elements;
+ $this->debug("Filtering by callback");
+ }
+ $newStack = array();
+ foreach ($this->elements as $index => $node) {
+ $result = phpQuery::callbackRun($callback, array($index, $node));
+ if (is_null($result) || (!is_null($result) && $result))
+ $newStack[] = $node;
+ }
+ $this->elements = $newStack;
+ return $_skipHistory
+ ? $this
+ : $this->newInstance();
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @link http://docs.jquery.com/Traversing/filter
+ */
+ public function filter($selectors, $_skipHistory = false)
+ {
+ if ($selectors instanceof Callback or $selectors instanceof Closure)
+ return $this->filterCallback($selectors, $_skipHistory);
+ if (!$_skipHistory)
+ $this->elementsBackup = $this->elements;
+ $notSimpleSelector = array(' ', '>', '~', '+', '/');
+ if (!is_array($selectors))
+ $selectors = $this->parseSelector($selectors);
+ if (!$_skipHistory)
+ $this->debug(array("Filtering:", $selectors));
+ $finalStack = array();
+ foreach ($selectors as $selector) {
+ $stack = array();
+ if (!$selector)
+ break;
+ // avoid first space or /
+ if (in_array($selector[0], $notSimpleSelector))
+ $selector = array_slice($selector, 1);
+ // PER NODE selector chunks
+ foreach ($this->stack() as $node) {
+ $break = false;
+ foreach ($selector as $s) {
+ if (!($node instanceof DOMELEMENT)) {
+ // all besides DOMElement
+ if ($s[0] == '[') {
+ $attr = trim($s, '[]');
+ if (mb_strpos($attr, '=')) {
+ list($attr, $val) = explode('=', $attr);
+ if ($attr == 'nodeType' && $node->nodeType != $val)
+ $break = true;
+ }
+ } else
+ $break = true;
+ } else {
+ // DOMElement only
+ // ID
+ if ($s[0] == '#') {
+ if ($node->getAttribute('id') != substr($s, 1))
+ $break = true;
+ // CLASSES
+ } else if ($s[0] == '.') {
+ if (!$this->matchClasses($s, $node))
+ $break = true;
+ // ATTRS
+ } else if ($s[0] == '[') {
+ // strip side brackets
+ $attr = trim($s, '[]');
+ if (mb_strpos($attr, '=')) {
+ list($attr, $val) = explode('=', $attr);
+ $val = self::unQuote($val);
+ if ($attr == 'nodeType') {
+ if ($val != $node->nodeType)
+ $break = true;
+ } else if ($this->isRegexp($attr)) {
+ $val = extension_loaded('mbstring') && phpQuery::$mbstringSupport
+ ? quotemeta(trim($val, '"\''))
+ : preg_quote(trim($val, '"\''), '@');
+ // switch last character
+ switch (substr($attr, -1)) {
+ // quotemeta used insted of preg_quote
+ // http://code.google.com/p/phpquery/issues/detail?id=76
+ case '^':
+ $pattern = '^' . $val;
+ break;
+ case '*':
+ $pattern = '.*' . $val . '.*';
+ break;
+ case '$':
+ $pattern = '.*' . $val . '$';
+ break;
+ }
+ // cut last character
+ $attr = substr($attr, 0, -1);
+ $isMatch = extension_loaded('mbstring') && phpQuery::$mbstringSupport
+ ? mb_ereg_match($pattern, $node->getAttribute($attr))
+ : preg_match("@{$pattern}@", $node->getAttribute($attr));
+ if (!$isMatch)
+ $break = true;
+ } else if ($node->getAttribute($attr) != $val)
+ $break = true;
+ } else if (!$node->hasAttribute($attr))
+ $break = true;
+ // PSEUDO CLASSES
+ } else if ($s[0] == ':') {
+ // skip
+ // TAG
+ } else if (trim($s)) {
+ if ($s != '*') {
+ // TODO namespaces
+ if (isset($node->tagName)) {
+ if ($node->tagName != $s)
+ $break = true;
+ } else if ($s == 'html' && !$this->isRoot($node))
+ $break = true;
+ }
+ // AVOID NON-SIMPLE SELECTORS
+ } else if (in_array($s, $notSimpleSelector)) {
+ $break = true;
+ $this->debug(array('Skipping non simple selector', $selector));
+ }
+ }
+ if ($break)
+ break;
+ }
+ // if element passed all chunks of selector - add it to new stack
+ if (!$break)
+ $stack[] = $node;
+ }
+ $tmpStack = $this->elements;
+ $this->elements = $stack;
+ // PER ALL NODES selector chunks
+ foreach ($selector as $s)
+ // PSEUDO CLASSES
+ if ($s[0] == ':')
+ $this->pseudoClasses($s);
+ foreach ($this->elements as $node)
+ // XXX it should be merged without duplicates
+ // but jQuery doesnt do that
+ $finalStack[] = $node;
+ $this->elements = $tmpStack;
+ }
+ $this->elements = $finalStack;
+ if ($_skipHistory) {
+ return $this;
+ } else {
+ $this->debug("Stack length after filter(): " . count($finalStack));
+ return $this->newInstance();
+ }
+ }
+ /**
+ *
+ * @param $value
+ * @return unknown_type
+ * @TODO implement in all methods using passed parameters
+ */
+ protected static function unQuote($value)
+ {
+ return $value[0] == '\'' || $value[0] == '"'
+ ? substr($value, 1, -1)
+ : $value;
+ }
+ /**
+ * Enter description here...
+ *
+ * @link http://docs.jquery.com/Ajax/load
+ * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo Support $selector
+ */
+ public function load($url, $data = null, $callback = null)
+ {
+ if ($data && !is_array($data)) {
+ $callback = $data;
+ $data = null;
+ }
+ if (mb_strpos($url, ' ') !== false) {
+ $matches = null;
+ if (extension_loaded('mbstring') && phpQuery::$mbstringSupport)
+ mb_ereg('^([^ ]+) (.*)$', $url, $matches);
+ else
+ preg_match('^([^ ]+) (.*)$', $url, $matches);
+ $url = $matches[1];
+ $selector = $matches[2];
+ // FIXME this sucks, pass as callback param
+ $this->_loadSelector = $selector;
+ }
+ $ajax = array(
+ 'url' => $url,
+ 'type' => $data ? 'POST' : 'GET',
+ 'data' => $data,
+ 'complete' => $callback,
+ 'success' => array($this, '__loadSuccess')
+ );
+ phpQuery::ajax($ajax);
+ return $this;
+ }
+ /**
+ * @access private
+ * @param $html
+ * @return unknown_type
+ */
+ public function __loadSuccess($html)
+ {
+ if ($this->_loadSelector) {
+ $html = phpQuery::newDocument($html)->find($this->_loadSelector);
+ unset($this->_loadSelector);
+ }
+ foreach ($this->stack(1) as $node) {
+ phpQuery::pq($node, $this->getDocumentID())
+ ->markup($html);
+ }
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo
+ */
+ public function css()
+ {
+ // TODO
+ return $this;
+ }
+ /**
+ * @todo
+ *
+ */
+ public function show()
+ {
+ // TODO
+ return $this;
+ }
+ /**
+ * @todo
+ *
+ */
+ public function hide()
+ {
+ // TODO
+ return $this;
+ }
+ /**
+ * Trigger a type of event on every matched element.
+ *
+ * @param unknown_type $type
+ * @param unknown_type $data
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @TODO support more than event in $type (space-separated)
+ */
+ public function trigger($type, $data = array())
+ {
+ foreach ($this->elements as $node)
+ phpQueryEvents::trigger($this->getDocumentID(), $type, $data, $node);
+ return $this;
+ }
+ /**
+ * This particular method triggers all bound event handlers on an element (for a specific event type) WITHOUT executing the browsers default actions.
+ *
+ * @param unknown_type $type
+ * @param unknown_type $data
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @TODO
+ */
+ public function triggerHandler($type, $data = array())
+ {
+ // TODO;
+ }
+ /**
+ * Binds a handler to one or more events (like click) for each matched element.
+ * Can also bind custom events.
+ *
+ * @param unknown_type $type
+ * @param unknown_type $data Optional
+ * @param unknown_type $callback
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @TODO support '!' (exclusive) events
+ * @TODO support more than event in $type (space-separated)
+ */
+ public function bind($type, $data, $callback = null)
+ {
+ // TODO check if $data is callable, not using is_callable
+ if (!isset($callback)) {
+ $callback = $data;
+ $data = null;
+ }
+ foreach ($this->elements as $node)
+ phpQueryEvents::add($this->getDocumentID(), $node, $type, $data, $callback);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @param unknown_type $type
+ * @param unknown_type $callback
+ * @return unknown
+ * @TODO namespace events
+ * @TODO support more than event in $type (space-separated)
+ */
+ public function unbind($type = null, $callback = null)
+ {
+ foreach ($this->elements as $node)
+ phpQueryEvents::remove($this->getDocumentID(), $node, $type, $callback);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function change($callback = null)
+ {
+ if ($callback)
+ return $this->bind('change', $callback);
+ return $this->trigger('change');
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function submit($callback = null)
+ {
+ if ($callback)
+ return $this->bind('submit', $callback);
+ return $this->trigger('submit');
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function click($callback = null)
+ {
+ if ($callback)
+ return $this->bind('click', $callback);
+ return $this->trigger('click');
+ }
+ /**
+ * Enter description here...
+ *
+ * @param String|phpQuery
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function wrapAllOld($wrapper)
+ {
+ $wrapper = pq($wrapper)->_clone();
+ if (!$wrapper->length() || !$this->length())
+ return $this;
+ $wrapper->insertBefore($this->elements[0]);
+ $deepest = $wrapper->elements[0];
+ while ($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
+ $deepest = $deepest->firstChild;
+ pq($deepest)->append($this);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * TODO testme...
+ * @param String|phpQuery
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function wrapAll($wrapper)
+ {
+ if (!$this->length())
+ return $this;
+ return phpQuery::pq($wrapper, $this->getDocumentID())
+ ->clone()
+ ->insertBefore($this->get(0))
+ ->map(array($this, '___wrapAllCallback'))
+ ->append($this);
+ }
+ /**
+ *
+ * @param $node
+ * @return unknown_type
+ * @access private
+ */
+ public function ___wrapAllCallback($node)
+ {
+ $deepest = $node;
+ while ($deepest->firstChild && $deepest->firstChild instanceof DOMELEMENT)
+ $deepest = $deepest->firstChild;
+ return $deepest;
+ }
+ /**
+ * Enter description here...
+ * NON JQUERY METHOD
+ *
+ * @param String|phpQuery
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function wrapAllPHP($codeBefore, $codeAfter)
+ {
+ return $this
+ ->slice(0, 1)
+ ->beforePHP($codeBefore)
+ ->end()
+ ->slice(-1)
+ ->afterPHP($codeAfter)
+ ->end();
+ }
+ /**
+ * Enter description here...
+ *
+ * @param String|phpQuery
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function wrap($wrapper)
+ {
+ foreach ($this->stack() as $node)
+ phpQuery::pq($node, $this->getDocumentID())->wrapAll($wrapper);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @param String|phpQuery
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function wrapPHP($codeBefore, $codeAfter)
+ {
+ foreach ($this->stack() as $node)
+ phpQuery::pq($node, $this->getDocumentID())->wrapAllPHP($codeBefore, $codeAfter);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @param String|phpQuery
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function wrapInner($wrapper)
+ {
+ foreach ($this->stack() as $node)
+ phpQuery::pq($node, $this->getDocumentID())->contents()->wrapAll($wrapper);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @param String|phpQuery
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function wrapInnerPHP($codeBefore, $codeAfter)
+ {
+ foreach ($this->stack(1) as $node)
+ phpQuery::pq($node, $this->getDocumentID())->contents()
+ ->wrapAllPHP($codeBefore, $codeAfter);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @testme Support for text nodes
+ */
+ public function contents()
+ {
+ $stack = array();
+ foreach ($this->stack(1) as $el) {
+ // FIXME (fixed) http://code.google.com/p/phpquery/issues/detail?id=56
+ // if (! isset($el->childNodes))
+ // continue;
+ foreach ($el->childNodes as $node) {
+ $stack[] = $node;
+ }
+ }
+ return $this->newInstance($stack);
+ }
+ /**
+ * Enter description here...
+ *
+ * jQuery difference.
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function contentsUnwrap()
+ {
+ foreach ($this->stack(1) as $node) {
+ if (!$node->parentNode)
+ continue;
+ $childNodes = array();
+ // any modification in DOM tree breaks childNodes iteration, so cache them first
+ foreach ($node->childNodes as $chNode)
+ $childNodes[] = $chNode;
+ foreach ($childNodes as $chNode)
+ // $node->parentNode->appendChild($chNode);
+ $node->parentNode->insertBefore($chNode, $node);
+ $node->parentNode->removeChild($node);
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * jQuery difference.
+ */
+ public function switchWith($markup)
+ {
+ $markup = pq($markup, $this->getDocumentID());
+ $content = null;
+ foreach ($this->stack(1) as $node) {
+ pq($node)
+ ->contents()->toReference($content)->end()
+ ->replaceWith($markup->clone()->append($content));
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function eq($num)
+ {
+ $oldStack = $this->elements;
+ $this->elementsBackup = $this->elements;
+ $this->elements = array();
+ if (isset($oldStack[$num]))
+ $this->elements[] = $oldStack[$num];
+ return $this->newInstance();
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function size()
+ {
+ return count($this->elements);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @deprecated Use length as attribute
+ */
+ public function length()
+ {
+ return $this->size();
+ }
+
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return $this->size();
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo $level
+ */
+ public function end($level = 1)
+ {
+ // $this->elements = array_pop( $this->history );
+ // return $this;
+ // $this->previous->DOM = $this->DOM;
+ // $this->previous->XPath = $this->XPath;
+ return $this->previous
+ ? $this->previous
+ : $this;
+ }
+ /**
+ * Enter description here...
+ * Normal use ->clone() .
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @access private
+ */
+ public function _clone()
+ {
+ $newStack = array();
+ //pr(array('copy... ', $this->whois()));
+ //$this->dumpHistory('copy');
+ $this->elementsBackup = $this->elements;
+ foreach ($this->elements as $node) {
+ $newStack[] = $node->cloneNode(true);
+ }
+ $this->elements = $newStack;
+ return $this->newInstance();
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function replaceWithPHP($code)
+ {
+ return $this->replaceWith(phpQuery::php($code));
+ }
+ /**
+ * Enter description here...
+ *
+ * @param String|phpQuery $content
+ * @link http://docs.jquery.com/Manipulation/replaceWith#content
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function replaceWith($content)
+ {
+ return $this->after($content)->remove();
+ }
+ /**
+ * Enter description here...
+ *
+ * @param String $selector
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo this works ?
+ */
+ public function replaceAll($selector)
+ {
+ foreach (phpQuery::pq($selector, $this->getDocumentID()) as $node)
+ phpQuery::pq($node, $this->getDocumentID())
+ ->after($this->_clone())
+ ->remove();
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function remove($selector = null)
+ {
+ $loop = $selector
+ ? $this->filter($selector)->elements
+ : $this->elements;
+ foreach ($loop as $node) {
+ if (!$node->parentNode)
+ continue;
+ if (isset($node->tagName))
+ $this->debug("Removing '{$node->tagName}'");
+ $node->parentNode->removeChild($node);
+ // Mutation event
+ $event = new DOMEvent(array(
+ 'target' => $node,
+ 'type' => 'DOMNodeRemoved'
+ ));
+ phpQueryEvents::trigger(
+ $this->getDocumentID(),
+ $event->type,
+ array($event),
+ $node
+ );
+ }
+ return $this;
+ }
+ protected function markupEvents($newMarkup, $oldMarkup, $node)
+ {
+ if ($node->tagName == 'textarea' && $newMarkup != $oldMarkup) {
+ $event = new DOMEvent(array(
+ 'target' => $node,
+ 'type' => 'change'
+ ));
+ phpQueryEvents::trigger(
+ $this->getDocumentID(),
+ $event->type,
+ array($event),
+ $node
+ );
+ }
+ }
+ /**
+ * jQuey difference
+ *
+ * @param $markup
+ * @return unknown_type
+ * @TODO trigger change event for textarea
+ */
+ public function markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ $args = func_get_args();
+ if ($this->documentWrapper->isXML)
+ return call_user_func_array(array($this, 'xml'), $args);
+ else
+ return call_user_func_array(array($this, 'html'), $args);
+ }
+ /**
+ * jQuey difference
+ *
+ * @param $markup
+ * @return unknown_type
+ */
+ public function markupOuter($callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ $args = func_get_args();
+ if ($this->documentWrapper->isXML)
+ return call_user_func_array(array($this, 'xmlOuter'), $args);
+ else
+ return call_user_func_array(array($this, 'htmlOuter'), $args);
+ }
+ /**
+ * Enter description here...
+ *
+ * @param unknown_type $html
+ * @return string|phpQuery|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @TODO force html result
+ */
+ public function html($html = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ if (isset($html)) {
+ // INSERT
+ $nodes = $this->documentWrapper->import($html);
+ $this->empty();
+ foreach ($this->stack(1) as $alreadyAdded => $node) {
+ // for now, limit events for textarea
+ if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
+ $oldHtml = pq($node, $this->getDocumentID())->markup();
+ foreach ($nodes as $newNode) {
+ $node->appendChild(
+ $alreadyAdded
+ ? $newNode->cloneNode(true)
+ : $newNode
+ );
+ }
+ // for now, limit events for textarea
+ if (($this->isXHTML() || $this->isHTML()) && $node->tagName == 'textarea')
+ $this->markupEvents($html, $oldHtml, $node);
+ }
+ return $this;
+ } else {
+ // FETCH
+ $return = $this->documentWrapper->markup($this->elements, true);
+ $args = func_get_args();
+ foreach (array_slice($args, 1) as $callback) {
+ $return = phpQuery::callbackRun($callback, array($return));
+ }
+ return $return;
+ }
+ }
+ /**
+ * @TODO force xml result
+ */
+ public function xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ $args = func_get_args();
+ return call_user_func_array(array($this, 'html'), $args);
+ }
+ /**
+ * Enter description here...
+ * @TODO force html result
+ *
+ * @return String
+ */
+ public function htmlOuter($callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ $markup = $this->documentWrapper->markup($this->elements);
+ // pass thou callbacks
+ $args = func_get_args();
+ foreach ($args as $callback) {
+ $markup = phpQuery::callbackRun($callback, array($markup));
+ }
+ return $markup;
+ }
+ /**
+ * @TODO force xml result
+ */
+ public function xmlOuter($callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ $args = func_get_args();
+ return call_user_func_array(array($this, 'htmlOuter'), $args);
+ }
+ public function __toString()
+ {
+ return $this->markupOuter();
+ }
+ /**
+ * Just like html(), but returns markup with VALID (dangerous) PHP tags.
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo support returning markup with PHP tags when called without param
+ */
+ public function php($code = null)
+ {
+ return $this->markupPHP($code);
+ }
+ /**
+ * Enter description here...
+ *
+ * @param $code
+ * @return unknown_type
+ */
+ public function markupPHP($code = null)
+ {
+ return isset($code)
+ ? $this->markup(phpQuery::php($code))
+ : phpQuery::markupToPHP($this->markup());
+ }
+ /**
+ * Enter description here...
+ *
+ * @param $code
+ * @return unknown_type
+ */
+ public function markupOuterPHP()
+ {
+ return phpQuery::markupToPHP($this->markupOuter());
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function children($selector = null)
+ {
+ $stack = array();
+ foreach ($this->stack(1) as $node) {
+ // foreach($node->getElementsByTagName('*') as $newNode) {
+ foreach ($node->childNodes as $newNode) {
+ if ($newNode->nodeType != 1)
+ continue;
+ if ($selector && !$this->is($selector, $newNode))
+ continue;
+ if ($this->elementsContainsNode($newNode, $stack))
+ continue;
+ $stack[] = $newNode;
+ }
+ }
+ $this->elementsBackup = $this->elements;
+ $this->elements = $stack;
+ return $this->newInstance();
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function ancestors($selector = null)
+ {
+ return $this->children($selector);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function append($content)
+ {
+ return $this->insert($content, __FUNCTION__);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function appendPHP($content)
+ {
+ return $this->insert(" ", 'append');
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function appendTo($seletor)
+ {
+ return $this->insert($seletor, __FUNCTION__);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function prepend($content)
+ {
+ return $this->insert($content, __FUNCTION__);
+ }
+ /**
+ * Enter description here...
+ *
+ * @todo accept many arguments, which are joined, arrays maybe also
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function prependPHP($content)
+ {
+ return $this->insert(" ", 'prepend');
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function prependTo($seletor)
+ {
+ return $this->insert($seletor, __FUNCTION__);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function before($content)
+ {
+ return $this->insert($content, __FUNCTION__);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function beforePHP($content)
+ {
+ return $this->insert(" ", 'before');
+ }
+ /**
+ * Enter description here...
+ *
+ * @param String|phpQuery
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function insertBefore($seletor)
+ {
+ return $this->insert($seletor, __FUNCTION__);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function after($content)
+ {
+ return $this->insert($content, __FUNCTION__);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function afterPHP($content)
+ {
+ return $this->insert(" ", 'after');
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function insertAfter($seletor)
+ {
+ return $this->insert($seletor, __FUNCTION__);
+ }
+ /**
+ * Internal insert method. Don't use it.
+ *
+ * @param unknown_type $target
+ * @param unknown_type $type
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @access private
+ */
+ public function insert($target, $type)
+ {
+ $this->debug("Inserting data with '{$type}'");
+ $to = false;
+ switch ($type) {
+ case 'appendTo':
+ case 'prependTo':
+ case 'insertBefore':
+ case 'insertAfter':
+ $to = true;
+ }
+ switch (gettype($target)) {
+ case 'string':
+ $insertFrom = $insertTo = array();
+ if ($to) {
+ // INSERT TO
+ $insertFrom = $this->elements;
+ if (phpQuery::isMarkup($target)) {
+ // $target is new markup, import it
+ $insertTo = $this->documentWrapper->import($target);
+ // insert into selected element
+ } else {
+ // $tagret is a selector
+ $thisStack = $this->elements;
+ $this->toRoot();
+ $insertTo = $this->find($target)->elements;
+ $this->elements = $thisStack;
+ }
+ } else {
+ // INSERT FROM
+ $insertTo = $this->elements;
+ $insertFrom = $this->documentWrapper->import($target);
+ }
+ break;
+ case 'object':
+ $insertFrom = $insertTo = array();
+ // phpQuery
+ if ($target instanceof self) {
+ if ($to) {
+ $insertTo = $target->elements;
+ if ($this->documentFragment && $this->stackIsRoot())
+ // get all body children
+ // $loop = $this->find('body > *')->elements;
+ // TODO test it, test it hard...
+ // $loop = $this->newInstance($this->root)->find('> *')->elements;
+ $loop = $this->root->childNodes;
+ else
+ $loop = $this->elements;
+ // import nodes if needed
+ $insertFrom = $this->getDocumentID() == $target->getDocumentID()
+ ? $loop
+ : $target->documentWrapper->import($loop);
+ } else {
+ $insertTo = $this->elements;
+ if ($target->documentFragment && $target->stackIsRoot())
+ // get all body children
+ // $loop = $target->find('body > *')->elements;
+ $loop = $target->root->childNodes;
+ else
+ $loop = $target->elements;
+ // import nodes if needed
+ $insertFrom = $this->getDocumentID() == $target->getDocumentID()
+ ? $loop
+ : $this->documentWrapper->import($loop);
+ }
+ // DOMNODE
+ } elseif ($target instanceof DOMNODE) {
+ // import node if needed
+ // if ( $target->ownerDocument != $this->DOM )
+ // $target = $this->DOM->importNode($target, true);
+ if ($to) {
+ $insertTo = array($target);
+ if ($this->documentFragment && $this->stackIsRoot())
+ // get all body children
+ $loop = $this->root->childNodes;
+ // $loop = $this->find('body > *')->elements;
+ else
+ $loop = $this->elements;
+ foreach ($loop as $fromNode)
+ // import nodes if needed
+ $insertFrom[] = !$fromNode->ownerDocument->isSameNode($target->ownerDocument)
+ ? $target->ownerDocument->importNode($fromNode, true)
+ : $fromNode;
+ } else {
+ // import node if needed
+ if (!$target->ownerDocument->isSameNode($this->document))
+ $target = $this->document->importNode($target, true);
+ $insertTo = $this->elements;
+ $insertFrom[] = $target;
+ }
+ }
+ break;
+ }
+ phpQuery::debug("From " . count($insertFrom) . "; To " . count($insertTo) . " nodes");
+ foreach ($insertTo as $insertNumber => $toNode) {
+ // we need static relative elements in some cases
+ switch ($type) {
+ case 'prependTo':
+ case 'prepend':
+ $firstChild = $toNode->firstChild;
+ break;
+ case 'insertAfter':
+ case 'after':
+ $nextSibling = $toNode->nextSibling;
+ break;
+ }
+ foreach ($insertFrom as $fromNode) {
+ // clone if inserted already before
+ $insert = $insertNumber
+ ? $fromNode->cloneNode(true)
+ : $fromNode;
+ switch ($type) {
+ case 'appendTo':
+ case 'append':
+ // $toNode->insertBefore(
+ // $fromNode,
+ // $toNode->lastChild->nextSibling
+ // );
+ $toNode->appendChild($insert);
+ $eventTarget = $insert;
+ break;
+ case 'prependTo':
+ case 'prepend':
+ $toNode->insertBefore(
+ $insert,
+ $firstChild
+ );
+ break;
+ case 'insertBefore':
+ case 'before':
+ if (!$toNode->parentNode)
+ throw new Exception("No parentNode, can't do {$type}()");
+ else
+ $toNode->parentNode->insertBefore(
+ $insert,
+ $toNode
+ );
+ break;
+ case 'insertAfter':
+ case 'after':
+ if (!$toNode->parentNode)
+ throw new Exception("No parentNode, can't do {$type}()");
+ else
+ $toNode->parentNode->insertBefore(
+ $insert,
+ $nextSibling
+ );
+ break;
+ }
+ // Mutation event
+ $event = new DOMEvent(array(
+ 'target' => $insert,
+ 'type' => 'DOMNodeInserted'
+ ));
+ phpQueryEvents::trigger(
+ $this->getDocumentID(),
+ $event->type,
+ array($event),
+ $insert
+ );
+ }
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return Int
+ */
+ public function index($subject)
+ {
+ $index = -1;
+ $subject = $subject instanceof phpQueryObject
+ ? $subject->elements[0]
+ : $subject;
+ foreach ($this->newInstance() as $k => $node) {
+ if ($node->isSameNode($subject))
+ $index = $k;
+ }
+ return $index;
+ }
+ /**
+ * Enter description here...
+ *
+ * @param unknown_type $start
+ * @param unknown_type $end
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @testme
+ */
+ public function slice($start, $end = null)
+ {
+ // $last = count($this->elements)-1;
+ // $end = $end
+ // ? min($end, $last)
+ // : $last;
+ // if ($start < 0)
+ // $start = $last+$start;
+ // if ($start > $last)
+ // return array();
+ if ($end > 0)
+ $end = $end - $start;
+ return $this->newInstance(
+ array_slice($this->elements, $start, $end)
+ );
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function reverse()
+ {
+ $this->elementsBackup = $this->elements;
+ $this->elements = array_reverse($this->elements);
+ return $this->newInstance();
+ }
+ /**
+ * Return joined text content.
+ * @return String
+ */
+ public function text($text = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ {
+ if (isset($text))
+ return $this->html(htmlspecialchars($text));
+ $args = func_get_args();
+ $args = array_slice($args, 1);
+ $return = '';
+ foreach ($this->elements as $node) {
+ $text = $node->textContent;
+ if (count($this->elements) > 1 && $text)
+ $text .= "\n";
+ foreach ($args as $callback) {
+ $text = phpQuery::callbackRun($callback, array($text));
+ }
+ $return .= $text;
+ }
+ return $return;
+ }
+ /**
+ * @return The text content of each matching element, like
+ * text() but returns an array with one entry per matched element.
+ * Read only.
+ */
+ public function texts($attr = null)
+ {
+ $results = array();
+ foreach ($this->elements as $node) {
+ $results[] = $node->textContent;
+ }
+ return $results;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function plugin($class, $file = null)
+ {
+ phpQuery::plugin($class, $file);
+ return $this;
+ }
+ /**
+ * Deprecated, use $pq->plugin() instead.
+ *
+ * @deprecated
+ * @param $class
+ * @param $file
+ * @return unknown_type
+ */
+ public static function extend($class, $file = null)
+ {
+ return $this->plugin($class, $file);
+ }
+ /**
+ *
+ * @access private
+ * @param $method
+ * @param $args
+ * @return unknown_type
+ */
+ public function __call($method, $args)
+ {
+ $aliasMethods = array('clone', 'empty');
+ if (isset(phpQuery::$extendMethods[$method])) {
+ array_unshift($args, $this);
+ return phpQuery::callbackRun(
+ phpQuery::$extendMethods[$method],
+ $args
+ );
+ } else if (isset(phpQuery::$pluginsMethods[$method])) {
+ array_unshift($args, $this);
+ $class = phpQuery::$pluginsMethods[$method];
+ $realClass = "phpQueryObjectPlugin_$class";
+ $return = call_user_func_array(
+ array($realClass, $method),
+ $args
+ );
+ // XXX deprecate ?
+ return is_null($return)
+ ? $this
+ : $return;
+ } else if (in_array($method, $aliasMethods)) {
+ return call_user_func_array(array($this, '_' . $method), $args);
+ } else
+ throw new Exception("Method '{$method}' doesnt exist");
+ }
+ /**
+ * Safe rename of next().
+ *
+ * Use it ONLY when need to call next() on an iterated object (in same time).
+ * Normaly there is no need to do such thing ;)
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @access private
+ */
+ public function _next($selector = null)
+ {
+ return $this->newInstance(
+ $this->getElementSiblings('nextSibling', $selector, true)
+ );
+ }
+ /**
+ * Use prev() and next().
+ *
+ * @deprecated
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @access private
+ */
+ public function _prev($selector = null)
+ {
+ return $this->prev($selector);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function prev($selector = null)
+ {
+ return $this->newInstance(
+ $this->getElementSiblings('previousSibling', $selector, true)
+ );
+ }
+ /**
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo
+ */
+ public function prevAll($selector = null)
+ {
+ return $this->newInstance(
+ $this->getElementSiblings('previousSibling', $selector)
+ );
+ }
+ /**
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo FIXME: returns source elements insted of next siblings
+ */
+ public function nextAll($selector = null)
+ {
+ return $this->newInstance(
+ $this->getElementSiblings('nextSibling', $selector)
+ );
+ }
+ /**
+ * @access private
+ */
+ protected function getElementSiblings($direction, $selector = null, $limitToOne = false)
+ {
+ $stack = array();
+ $count = 0;
+ foreach ($this->stack() as $node) {
+ $test = $node;
+ while (isset($test->{$direction}) && $test->{$direction}) {
+ $test = $test->{$direction};
+ if (!$test instanceof DOMELEMENT)
+ continue;
+ $stack[] = $test;
+ if ($limitToOne)
+ break;
+ }
+ }
+ if ($selector) {
+ $stackOld = $this->elements;
+ $this->elements = $stack;
+ $stack = $this->filter($selector, true)->stack();
+ $this->elements = $stackOld;
+ }
+ return $stack;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function siblings($selector = null)
+ {
+ $stack = array();
+ $siblings = array_merge(
+ $this->getElementSiblings('previousSibling', $selector),
+ $this->getElementSiblings('nextSibling', $selector)
+ );
+ foreach ($siblings as $node) {
+ if (!$this->elementsContainsNode($node, $stack))
+ $stack[] = $node;
+ }
+ return $this->newInstance($stack);
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function not($selector = null)
+ {
+ if (is_string($selector))
+ phpQuery::debug(array('not', $selector));
+ else
+ phpQuery::debug('not');
+ $stack = array();
+ if ($selector instanceof self || $selector instanceof DOMNODE) {
+ foreach ($this->stack() as $node) {
+ if ($selector instanceof self) {
+ $matchFound = false;
+ foreach ($selector->stack() as $notNode) {
+ if ($notNode->isSameNode($node))
+ $matchFound = true;
+ }
+ if (!$matchFound)
+ $stack[] = $node;
+ } else if ($selector instanceof DOMNODE) {
+ if (!$selector->isSameNode($node))
+ $stack[] = $node;
+ } else {
+ if (!$this->is($selector))
+ $stack[] = $node;
+ }
+ }
+ } else {
+ $orgStack = $this->stack();
+ $matched = $this->filter($selector, true)->stack();
+ // $matched = array();
+ // // simulate OR in filter() instead of AND 5y
+ // foreach($this->parseSelector($selector) as $s) {
+ // $matched = array_merge($matched,
+ // $this->filter(array($s))->stack()
+ // );
+ // }
+ foreach ($orgStack as $node)
+ if (!$this->elementsContainsNode($node, $matched))
+ $stack[] = $node;
+ }
+ return $this->newInstance($stack);
+ }
+ /**
+ * Enter description here...
+ *
+ * @param string|phpQueryObject
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function add($selector = null)
+ {
+ if (!$selector)
+ return $this;
+ $stack = array();
+ $this->elementsBackup = $this->elements;
+ $found = phpQuery::pq($selector, $this->getDocumentID());
+ $this->merge($found->elements);
+ return $this->newInstance();
+ }
+ /**
+ * @access private
+ */
+ protected function merge()
+ {
+ foreach (func_get_args() as $nodes)
+ foreach ($nodes as $newNode)
+ if (!$this->elementsContainsNode($newNode))
+ $this->elements[] = $newNode;
+ }
+ /**
+ * @access private
+ * TODO refactor to stackContainsNode
+ */
+ protected function elementsContainsNode($nodeToCheck, $elementsStack = null)
+ {
+ $loop = !is_null($elementsStack)
+ ? $elementsStack
+ : $this->elements;
+ foreach ($loop as $node) {
+ if ($node->isSameNode($nodeToCheck))
+ return true;
+ }
+ return false;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function parent($selector = null)
+ {
+ $stack = array();
+ foreach ($this->elements as $node)
+ if ($node->parentNode && !$this->elementsContainsNode($node->parentNode, $stack))
+ $stack[] = $node->parentNode;
+ $this->elementsBackup = $this->elements;
+ $this->elements = $stack;
+ if ($selector)
+ $this->filter($selector, true);
+ return $this->newInstance();
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function parents($selector = null)
+ {
+ $stack = array();
+ if (!$this->elements)
+ $this->debug('parents() - stack empty');
+ foreach ($this->elements as $node) {
+ $test = $node;
+ while ($test->parentNode) {
+ $test = $test->parentNode;
+ if ($this->isRoot($test))
+ break;
+ if (!$this->elementsContainsNode($test, $stack)) {
+ $stack[] = $test;
+ continue;
+ }
+ }
+ }
+ $this->elementsBackup = $this->elements;
+ $this->elements = $stack;
+ if ($selector)
+ $this->filter($selector, true);
+ return $this->newInstance();
+ }
+ /**
+ * Internal stack iterator.
+ *
+ * @access private
+ */
+ public function stack($nodeTypes = null)
+ {
+ if (!isset($nodeTypes))
+ return $this->elements;
+ if (!is_array($nodeTypes))
+ $nodeTypes = array($nodeTypes);
+ $return = array();
+ foreach ($this->elements as $node) {
+ if (in_array($node->nodeType, $nodeTypes))
+ $return[] = $node;
+ }
+ return $return;
+ }
+ // TODO phpdoc; $oldAttr is result of hasAttribute, before any changes
+ protected function attrEvents($attr, $oldAttr, $oldValue, $node)
+ {
+ // skip events for XML documents
+ if (!$this->isXHTML() && !$this->isHTML())
+ return;
+ $event = null;
+ // identify
+ $isInputValue = $node->tagName == 'input'
+ && (in_array(
+ $node->getAttribute('type'),
+ array('text', 'password', 'hidden')
+ )
+ || !$node->getAttribute('type')
+ );
+ $isRadio = $node->tagName == 'input'
+ && $node->getAttribute('type') == 'radio';
+ $isCheckbox = $node->tagName == 'input'
+ && $node->getAttribute('type') == 'checkbox';
+ $isOption = $node->tagName == 'option';
+ if ($isInputValue && $attr == 'value' && $oldValue != $node->getAttribute($attr)) {
+ $event = new DOMEvent(array(
+ 'target' => $node,
+ 'type' => 'change'
+ ));
+ } else if (($isRadio || $isCheckbox) && $attr == 'checked' && (
+ // check
+ (!$oldAttr && $node->hasAttribute($attr))
+ // un-check
+ || (!$node->hasAttribute($attr) && $oldAttr)
+ )) {
+ $event = new DOMEvent(array(
+ 'target' => $node,
+ 'type' => 'change'
+ ));
+ } else if ($isOption && $node->parentNode && $attr == 'selected' && (
+ // select
+ (!$oldAttr && $node->hasAttribute($attr))
+ // un-select
+ || (!$node->hasAttribute($attr) && $oldAttr)
+ )) {
+ $event = new DOMEvent(array(
+ 'target' => $node->parentNode,
+ 'type' => 'change'
+ ));
+ }
+ if ($event) {
+ phpQueryEvents::trigger(
+ $this->getDocumentID(),
+ $event->type,
+ array($event),
+ $node
+ );
+ }
+ }
+ public function attr($attr = null, $value = null)
+ {
+ foreach ($this->stack(1) as $node) {
+ if (!is_null($value)) {
+ $loop = $attr == '*'
+ ? $this->getNodeAttrs($node)
+ : array($attr);
+ foreach ($loop as $a) {
+ $oldValue = $node->getAttribute($a);
+ $oldAttr = $node->hasAttribute($a);
+ // TODO raises an error when charset other than UTF-8
+ // while document's charset is also not UTF-8
+ @$node->setAttribute($a, $value);
+ $this->attrEvents($a, $oldAttr, $oldValue, $node);
+ }
+ } else if ($attr == '*') {
+ // jQuery difference
+ $return = array();
+ foreach ($node->attributes as $n => $v)
+ $return[$n] = $v->value;
+ return $return;
+ } else
+ return $node->hasAttribute($attr)
+ ? $node->getAttribute($attr)
+ : null;
+ }
+ return is_null($value)
+ ? '' : $this;
+ }
+ /**
+ * @return The same attribute of each matching element, like
+ * attr() but returns an array with one entry per matched element.
+ * Read only.
+ */
+ public function attrs($attr = null)
+ {
+ $results = array();
+ foreach ($this->stack(1) as $node) {
+ $results[] = $node->hasAttribute($attr)
+ ? $node->getAttribute($attr)
+ : null;
+ }
+ return $results;
+ }
+ /**
+ * @access private
+ */
+ protected function getNodeAttrs($node)
+ {
+ $return = array();
+ foreach ($node->attributes as $n => $o)
+ $return[] = $n;
+ return $return;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo check CDATA ???
+ */
+ public function attrPHP($attr, $code)
+ {
+ if (!is_null($code)) {
+ $value = '<' . '?php ' . $code . ' ?' . '>';
+ // TODO tempolary solution
+ // http://code.google.com/p/phpquery/issues/detail?id=17
+ // if (function_exists('mb_detect_encoding') && mb_detect_encoding($value) == 'ASCII')
+ // $value = mb_convert_encoding($value, 'UTF-8', 'HTML-ENTITIES');
+ }
+ foreach ($this->stack(1) as $node) {
+ if (!is_null($code)) {
+ // $attrNode = $this->DOM->createAttribute($attr);
+ $node->setAttribute($attr, $value);
+ // $attrNode->value = $value;
+ // $node->appendChild($attrNode);
+ } else if ($attr == '*') {
+ // jQuery diff
+ $return = array();
+ foreach ($node->attributes as $n => $v)
+ $return[$n] = $v->value;
+ return $return;
+ } else
+ return $node->getAttribute($attr);
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function removeAttr($attr)
+ {
+ foreach ($this->stack(1) as $node) {
+ $loop = $attr == '*'
+ ? $this->getNodeAttrs($node)
+ : array($attr);
+ foreach ($loop as $a) {
+ $oldValue = $node->getAttribute($a);
+ $node->removeAttribute($a);
+ $this->attrEvents($a, $oldValue, null, $node);
+ }
+ }
+ return $this;
+ }
+ /**
+ * Return form element value.
+ *
+ * @return String Fields value.
+ */
+ public function val($val = null)
+ {
+ if (!isset($val)) {
+ if ($this->eq(0)->is('select')) {
+ $selected = $this->eq(0)->find('option[selected=selected]');
+ if ($selected->is('[value]'))
+ return $selected->attr('value');
+ else
+ return $selected->text();
+ } else if ($this->eq(0)->is('textarea'))
+ return $this->eq(0)->markup();
+ else
+ return $this->eq(0)->attr('value');
+ } else {
+ $_val = null;
+ foreach ($this->stack(1) as $node) {
+ $node = pq($node, $this->getDocumentID());
+ if (is_array($val) && in_array($node->attr('type'), array('checkbox', 'radio'))) {
+ $isChecked = in_array($node->attr('value'), $val)
+ || in_array($node->attr('name'), $val);
+ if ($isChecked)
+ $node->attr('checked', 'checked');
+ else
+ $node->removeAttr('checked');
+ } else if ($node->get(0)->tagName == 'select') {
+ if (!isset($_val)) {
+ $_val = array();
+ if (!is_array($val))
+ $_val = array((string)$val);
+ else
+ foreach ($val as $v)
+ $_val[] = $v;
+ }
+ foreach ($node['option']->stack(1) as $option) {
+ $option = pq($option, $this->getDocumentID());
+ $selected = false;
+ // XXX: workaround for string comparsion, see issue #96
+ // http://code.google.com/p/phpquery/issues/detail?id=96
+ $selected = is_null($option->attr('value'))
+ ? in_array($option->markup(), $_val)
+ : in_array($option->attr('value'), $_val);
+ // $optionValue = $option->attr('value');
+ // $optionText = $option->text();
+ // $optionTextLenght = mb_strlen($optionText);
+ // foreach($_val as $v)
+ // if ($optionValue == $v)
+ // $selected = true;
+ // else if ($optionText == $v && $optionTextLenght == mb_strlen($v))
+ // $selected = true;
+ if ($selected)
+ $option->attr('selected', 'selected');
+ else
+ $option->removeAttr('selected');
+ }
+ } else if ($node->get(0)->tagName == 'textarea')
+ $node->markup($val);
+ else
+ $node->attr('value', $val);
+ }
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function andSelf()
+ {
+ if ($this->previous)
+ $this->elements = array_merge($this->elements, $this->previous->elements);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function addClass($className)
+ {
+ if (!$className)
+ return $this;
+ foreach ($this->stack(1) as $node) {
+ if (!$this->is(".$className", $node))
+ $node->setAttribute(
+ 'class',
+ trim($node->getAttribute('class') . ' ' . $className)
+ );
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function addClassPHP($className)
+ {
+ foreach ($this->stack(1) as $node) {
+ $classes = $node->getAttribute('class');
+ $newValue = $classes
+ ? $classes . ' <' . '?php ' . $className . ' ?' . '>'
+ : '<' . '?php ' . $className . ' ?' . '>';
+ $node->setAttribute('class', $newValue);
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @param string $className
+ * @return bool
+ */
+ public function hasClass($className)
+ {
+ foreach ($this->stack(1) as $node) {
+ if ($this->is(".$className", $node))
+ return true;
+ }
+ return false;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function removeClass($className)
+ {
+ foreach ($this->stack(1) as $node) {
+ $classes = explode(' ', $node->getAttribute('class'));
+ if (in_array($className, $classes)) {
+ $classes = array_diff($classes, array($className));
+ if ($classes)
+ $node->setAttribute('class', implode(' ', $classes));
+ else
+ $node->removeAttribute('class');
+ }
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function toggleClass($className)
+ {
+ foreach ($this->stack(1) as $node) {
+ if ($this->is($node, '.' . $className))
+ $this->removeClass($className);
+ else
+ $this->addClass($className);
+ }
+ return $this;
+ }
+ /**
+ * Proper name without underscore (just ->empty()) also works.
+ *
+ * Removes all child nodes from the set of matched elements.
+ *
+ * Example:
+ * pq("p")._empty()
+ *
+ * HTML:
+ * Hello, Person and person
+ *
+ * Result:
+ * [
]
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @access private
+ */
+ public function _empty()
+ {
+ foreach ($this->stack(1) as $node) {
+ // thx to 'dave at dgx dot cz'
+ $node->nodeValue = '';
+ }
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @param array|string $callback Expects $node as first param, $index as second
+ * @param array $scope External variables passed to callback. Use compact('varName1', 'varName2'...) and extract($scope)
+ * @param array $arg1 Will ba passed as third and futher args to callback.
+ * @param array $arg2 Will ba passed as fourth and futher args to callback, and so on...
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function each($callback, $param1 = null, $param2 = null, $param3 = null)
+ {
+ $paramStructure = null;
+ if (func_num_args() > 1) {
+ $paramStructure = func_get_args();
+ $paramStructure = array_slice($paramStructure, 1);
+ }
+ foreach ($this->elements as $v)
+ phpQuery::callbackRun($callback, array($v), $paramStructure);
+ return $this;
+ }
+ /**
+ * Run callback on actual object.
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public function callback($callback, $param1 = null, $param2 = null, $param3 = null)
+ {
+ $params = func_get_args();
+ $params[0] = $this;
+ phpQuery::callbackRun($callback, $params);
+ return $this;
+ }
+ /**
+ * Enter description here...
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @todo add $scope and $args as in each() ???
+ */
+ public function map($callback, $param1 = null, $param2 = null, $param3 = null)
+ {
+ // $stack = array();
+ //// foreach($this->newInstance() as $node) {
+ // foreach($this->newInstance() as $node) {
+ // $result = call_user_func($callback, $node);
+ // if ($result)
+ // $stack[] = $result;
+ // }
+ $params = func_get_args();
+ array_unshift($params, $this->elements);
+ return $this->newInstance(
+ call_user_func_array(array('phpQuery', 'map'), $params)
+ // phpQuery::map($this->elements, $callback)
+ );
+ }
+ /**
+ * Enter description here...
+ *
+ * @param $key
+ * @param $value
+ */
+ public function data($key, $value = null)
+ {
+ if (!isset($value)) {
+ // TODO? implement specific jQuery behavior od returning parent values
+ // is child which we look up doesn't exist
+ return phpQuery::data($this->get(0), $key, $value, $this->getDocumentID());
+ } else {
+ foreach ($this as $node)
+ phpQuery::data($node, $key, $value, $this->getDocumentID());
+ return $this;
+ }
+ }
+ /**
+ * Enter description here...
+ *
+ * @param $key
+ */
+ public function removeData($key)
+ {
+ foreach ($this as $node)
+ phpQuery::removeData($node, $key, $this->getDocumentID());
+ return $this;
+ }
+ // INTERFACE IMPLEMENTATIONS
+
+ // ITERATOR INTERFACE
+ /**
+ * @access private
+ */
+ #[\ReturnTypeWillChange]
+ public function rewind()
+ {
+ $this->debug('iterating foreach');
+ // phpQuery::selectDocument($this->getDocumentID());
+ $this->elementsBackup = $this->elements;
+ $this->elementsInterator = $this->elements;
+ $this->valid = isset($this->elements[0])
+ ? 1 : 0;
+ // $this->elements = $this->valid
+ // ? array($this->elements[0])
+ // : array();
+ $this->current = 0;
+ }
+ /**
+ * @access private
+ */
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ return $this->elementsInterator[$this->current];
+ }
+ /**
+ * @access private
+ */
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->current;
+ }
+ /**
+ * Double-function method.
+ *
+ * First: main iterator interface method.
+ * Second: Returning next sibling, alias for _next().
+ *
+ * Proper functionality is choosed automagicaly.
+ *
+ * @see phpQueryObject::_next()
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ #[\ReturnTypeWillChange]
+ public function next($cssSelector = null)
+ {
+ // if ($cssSelector || $this->valid)
+ // return $this->_next($cssSelector);
+ $this->valid = isset($this->elementsInterator[$this->current + 1])
+ ? true
+ : false;
+ if (!$this->valid && $this->elementsInterator) {
+ $this->elementsInterator = null;
+ } else if ($this->valid) {
+ $this->current++;
+ } else {
+ return $this->_next($cssSelector);
+ }
+ }
+ /**
+ * @access private
+ */
+ #[\ReturnTypeWillChange]
+ public function valid()
+ {
+ return $this->valid;
+ }
+ // ITERATOR INTERFACE END
+ // ARRAYACCESS INTERFACE
+ /**
+ * @access private
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($offset)
+ {
+ return $this->find($offset)->size() > 0;
+ }
+ /**
+ * @access private
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($offset)
+ {
+ return $this->find($offset);
+ }
+ /**
+ * @access private
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet($offset, $value)
+ {
+ // $this->find($offset)->replaceWith($value);
+ $this->find($offset)->html($value);
+ }
+ /**
+ * @access private
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($offset)
+ {
+ // empty
+ throw new Exception("Can't do unset, use array interface only for calling queries and replacing HTML.");
+ }
+ // ARRAYACCESS INTERFACE END
+ /**
+ * Returns node's XPath.
+ *
+ * @param unknown_type $oneNode
+ * @return string
+ * @TODO use native getNodePath is avaible
+ * @access private
+ */
+ protected function getNodeXpath($oneNode = null, $namespace = null)
+ {
+ $return = array();
+ $loop = $oneNode
+ ? array($oneNode)
+ : $this->elements;
+ // if ($namespace)
+ // $namespace .= ':';
+ foreach ($loop as $node) {
+ if ($node instanceof DOMDOCUMENT) {
+ $return[] = '';
+ continue;
+ }
+ $xpath = array();
+ while (!($node instanceof DOMDOCUMENT)) {
+ $i = 1;
+ $sibling = $node;
+ while ($sibling->previousSibling) {
+ $sibling = $sibling->previousSibling;
+ $isElement = $sibling instanceof DOMELEMENT;
+ if ($isElement && $sibling->tagName == $node->tagName)
+ $i++;
+ }
+ $xpath[] = $this->isXML()
+ ? "*[local-name()='{$node->tagName}'][{$i}]"
+ : "{$node->tagName}[{$i}]";
+ $node = $node->parentNode;
+ }
+ $xpath = implode('/', array_reverse($xpath));
+ $return[] = '/' . $xpath;
+ }
+ return $oneNode
+ ? $return[0]
+ : $return;
+ }
+ // HELPERS
+ public function whois($oneNode = null)
+ {
+ $return = array();
+ $loop = $oneNode
+ ? array($oneNode)
+ : $this->elements;
+ foreach ($loop as $node) {
+ if (isset($node->tagName)) {
+ $tag = in_array($node->tagName, array('php', 'js'))
+ ? strtoupper($node->tagName)
+ : $node->tagName;
+ $return[] = $tag
+ . ($node->getAttribute('id')
+ ? '#' . $node->getAttribute('id') : '')
+ . ($node->getAttribute('class')
+ ? '.' . implode('.', explode(' ', $node->getAttribute('class'))) : '')
+ . ($node->getAttribute('name')
+ ? '[name="' . $node->getAttribute('name') . '"]' : '')
+ . ($node->getAttribute('value') && strpos($node->getAttribute('value'), '<' . '?php') === false
+ ? '[value="' . substr(str_replace("\n", '', $node->getAttribute('value')), 0, 15) . '"]' : '')
+ . ($node->getAttribute('value') && strpos($node->getAttribute('value'), '<' . '?php') !== false
+ ? '[value=PHP]' : '')
+ . ($node->getAttribute('selected')
+ ? '[selected]' : '')
+ . ($node->getAttribute('checked')
+ ? '[checked]' : '');
+ } else if ($node instanceof DOMTEXT) {
+ if (trim($node->textContent))
+ $return[] = 'Text:' . substr(str_replace("\n", ' ', $node->textContent), 0, 15);
+ } else {
+ }
+ }
+ return $oneNode && isset($return[0])
+ ? $return[0]
+ : $return;
+ }
+ /**
+ * Dump htmlOuter and preserve chain. Usefull for debugging.
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ *
+ */
+ public function dump()
+ {
+ print 'DUMP #' . (phpQuery::$dumpCount++) . ' ';
+ $debug = phpQuery::$debug;
+ phpQuery::$debug = false;
+ // print __FILE__.':'.__LINE__."\n";
+ var_dump($this->htmlOuter());
+ return $this;
+ }
+ public function dumpWhois()
+ {
+ print 'DUMP #' . (phpQuery::$dumpCount++) . ' ';
+ $debug = phpQuery::$debug;
+ phpQuery::$debug = false;
+ // print __FILE__.':'.__LINE__."\n";
+ var_dump('whois', $this->whois());
+ phpQuery::$debug = $debug;
+ return $this;
+ }
+ public function dumpLength()
+ {
+ print 'DUMP #' . (phpQuery::$dumpCount++) . ' ';
+ $debug = phpQuery::$debug;
+ phpQuery::$debug = false;
+ // print __FILE__.':'.__LINE__."\n";
+ var_dump('length', $this->length());
+ phpQuery::$debug = $debug;
+ return $this;
+ }
+ public function dumpTree($html = true, $title = true)
+ {
+ $output = $title
+ ? 'DUMP #' . (phpQuery::$dumpCount++) . " \n" : '';
+ $debug = phpQuery::$debug;
+ phpQuery::$debug = false;
+ foreach ($this->stack() as $node)
+ $output .= $this->__dumpTree($node);
+ phpQuery::$debug = $debug;
+ print $html
+ ? nl2br(str_replace(' ', ' ', $output))
+ : $output;
+ return $this;
+ }
+ private function __dumpTree($node, $intend = 0)
+ {
+ $whois = $this->whois($node);
+ $return = '';
+ if ($whois)
+ $return .= str_repeat(' - ', $intend) . $whois . "\n";
+ if (isset($node->childNodes))
+ foreach ($node->childNodes as $chNode)
+ $return .= $this->__dumpTree($chNode, $intend + 1);
+ return $return;
+ }
+ /**
+ * Dump htmlOuter and stop script execution. Usefull for debugging.
+ *
+ */
+ public function dumpDie()
+ {
+ print __FILE__ . ':' . __LINE__;
+ var_dump($this->htmlOuter());
+ die();
+ }
+}
+
+
+// -- Multibyte Compatibility functions ---------------------------------------
+// http://svn.iphonewebdev.com/lace/lib/mb_compat.php
+
+/**
+ * mb_internal_encoding()
+ *
+ * Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_internal_encoding')) {
+ function mb_internal_encoding($enc)
+ {
+ return true;
+ }
+}
+
+/**
+ * mb_regex_encoding()
+ *
+ * Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_regex_encoding')) {
+ function mb_regex_encoding($enc)
+ {
+ return true;
+ }
+}
+
+/**
+ * mb_strlen()
+ *
+ * Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_strlen')) {
+ function mb_strlen($str)
+ {
+ return strlen($str);
+ }
+}
+
+/**
+ * mb_strpos()
+ *
+ * Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_strpos')) {
+ function mb_strpos($haystack, $needle, $offset = 0)
+ {
+ return strpos($haystack, $needle, $offset);
+ }
+}
+/**
+ * mb_stripos()
+ *
+ * Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_stripos')) {
+ function mb_stripos($haystack, $needle, $offset = 0)
+ {
+ return stripos($haystack, $needle, $offset);
+ }
+}
+
+/**
+ * mb_substr()
+ *
+ * Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_substr')) {
+ function mb_substr($str, $start, $length = 0)
+ {
+ return substr($str, $start, $length);
+ }
+}
+
+/**
+ * mb_substr_count()
+ *
+ * Included for mbstring pseudo-compatability.
+ */
+if (!function_exists('mb_substr_count')) {
+ function mb_substr_count($haystack, $needle)
+ {
+ return substr_count($haystack, $needle);
+ }
+}
+
+
+/**
+ * Static namespace for phpQuery functions.
+ *
+ * @author Tobiasz Cudnik
+ * @package phpQuery
+ */
+abstract class phpQuery
+{
+ /**
+ * XXX: Workaround for mbstring problems
+ *
+ * @var bool
+ */
+ public static $mbstringSupport = true;
+ public static $debug = false;
+ public static $documents = array();
+ public static $defaultDocumentID = null;
+ // public static $defaultDoctype = 'html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"';
+ /**
+ * Applies only to HTML.
+ *
+ * @var unknown_type
+ */
+ public static $defaultDoctype = '';
+ public static $defaultCharset = 'UTF-8';
+ /**
+ * Static namespace for plugins.
+ *
+ * @var object
+ */
+ public static $plugins = array();
+ /**
+ * List of loaded plugins.
+ *
+ * @var unknown_type
+ */
+ public static $pluginsLoaded = array();
+ public static $pluginsMethods = array();
+ public static $pluginsStaticMethods = array();
+ public static $extendMethods = array();
+ /**
+ * @TODO implement
+ */
+ public static $extendStaticMethods = array();
+ /**
+ * Hosts allowed for AJAX connections.
+ * Dot '.' means $_SERVER['HTTP_HOST'] (if any).
+ *
+ * @var array
+ */
+ public static $ajaxAllowedHosts = array(
+ '.'
+ );
+ /**
+ * AJAX settings.
+ *
+ * @var array
+ * XXX should it be static or not ?
+ */
+ public static $ajaxSettings = array(
+ 'url' => '', //TODO
+ 'global' => true,
+ 'type' => "GET",
+ 'timeout' => null,
+ 'contentType' => "application/x-www-form-urlencoded",
+ 'processData' => true,
+ // 'async' => true,
+ 'data' => null,
+ 'username' => null,
+ 'password' => null,
+ 'accepts' => array(
+ 'xml' => "application/xml, text/xml",
+ 'html' => "text/html",
+ 'script' => "text/javascript, application/javascript",
+ 'json' => "application/json, text/javascript",
+ 'text' => "text/plain",
+ '_default' => "*/*"
+ )
+ );
+ public static $lastModified = null;
+ public static $active = 0;
+ public static $dumpCount = 0;
+ /**
+ * Multi-purpose function.
+ * Use pq() as shortcut.
+ *
+ * In below examples, $pq is any result of pq(); function.
+ *
+ * 1. Import markup into existing document (without any attaching):
+ * - Import into selected document:
+ * pq('
') // DOESNT accept text nodes at beginning of input string !
+ * - Import into document with ID from $pq->getDocumentID():
+ * pq('
', $pq->getDocumentID())
+ * - Import into same document as DOMNode belongs to:
+ * pq('
', DOMNode)
+ * - Import into document from phpQuery object:
+ * pq('
', $pq)
+ *
+ * 2. Run query:
+ * - Run query on last selected document:
+ * pq('div.myClass')
+ * - Run query on document with ID from $pq->getDocumentID():
+ * pq('div.myClass', $pq->getDocumentID())
+ * - Run query on same document as DOMNode belongs to and use node(s)as root for query:
+ * pq('div.myClass', DOMNode)
+ * - Run query on document from phpQuery object
+ * and use object's stack as root node(s) for query:
+ * pq('div.myClass', $pq)
+ *
+ * @param string|DOMNode|DOMNodeList|array $arg1 HTML markup, CSS Selector, DOMNode or array of DOMNodes
+ * @param string|phpQueryObject|DOMNode $context DOM ID from $pq->getDocumentID(), phpQuery object (determines also query root) or DOMNode (determines also query root)
+ *
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery|QueryTemplatesPhpQuery|false
+ * phpQuery object or false in case of error.
+ */
+ public static function pq($arg1, $context = null)
+ {
+ if ($arg1 instanceof DOMNODE && !isset($context)) {
+ foreach (phpQuery::$documents as $documentWrapper) {
+ $compare = $arg1 instanceof DOMDocument
+ ? $arg1 : $arg1->ownerDocument;
+ if ($documentWrapper->document->isSameNode($compare))
+ $context = $documentWrapper->id;
+ }
+ }
+ if (!$context) {
+ $domId = self::$defaultDocumentID;
+ if (!$domId)
+ throw new Exception("Can't use last created DOM, because there isn't any. Use phpQuery::newDocument() first.");
+ // } else if (is_object($context) && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
+ } else if (is_object($context) && $context instanceof phpQueryObject)
+ $domId = $context->getDocumentID();
+ else if ($context instanceof DOMDOCUMENT) {
+ $domId = self::getDocumentID($context);
+ if (!$domId) {
+ //throw new Exception('Orphaned DOMDocument');
+ $domId = self::newDocument($context)->getDocumentID();
+ }
+ } else if ($context instanceof DOMNODE) {
+ $domId = self::getDocumentID($context);
+ if (!$domId) {
+ throw new Exception('Orphaned DOMNode');
+ // $domId = self::newDocument($context->ownerDocument);
+ }
+ } else
+ $domId = $context;
+ if ($arg1 instanceof phpQueryObject) {
+ // if (is_object($arg1) && (get_class($arg1) == 'phpQueryObject' || $arg1 instanceof PHPQUERY || is_subclass_of($arg1, 'phpQueryObject'))) {
+ /**
+ * Return $arg1 or import $arg1 stack if document differs:
+ * pq(pq('
'))
+ */
+ if ($arg1->getDocumentID() == $domId)
+ return $arg1;
+ $class = get_class($arg1);
+ // support inheritance by passing old object to overloaded constructor
+ $phpQuery = $class != 'phpQuery'
+ ? new $class($arg1, $domId)
+ : new phpQueryObject($domId);
+ $phpQuery->elements = array();
+ foreach ($arg1->elements as $node)
+ $phpQuery->elements[] = $phpQuery->document->importNode($node, true);
+ return $phpQuery;
+ } else if ($arg1 instanceof DOMNODE || (is_array($arg1) && isset($arg1[0]) && $arg1[0] instanceof DOMNODE)) {
+ /*
+ * Wrap DOM nodes with phpQuery object, import into document when needed:
+ * pq(array($domNode1, $domNode2))
+ */
+ $phpQuery = new phpQueryObject($domId);
+ if (!($arg1 instanceof DOMNODELIST) && !is_array($arg1))
+ $arg1 = array($arg1);
+ $phpQuery->elements = array();
+ foreach ($arg1 as $node) {
+ $sameDocument = $node->ownerDocument instanceof DOMDOCUMENT
+ && !$node->ownerDocument->isSameNode($phpQuery->document);
+ $phpQuery->elements[] = $sameDocument
+ ? $phpQuery->document->importNode($node, true)
+ : $node;
+ }
+ return $phpQuery;
+ } else if (self::isMarkup($arg1)) {
+ /**
+ * Import HTML:
+ * pq('
')
+ */
+ $phpQuery = new phpQueryObject($domId);
+ return $phpQuery->newInstance(
+ $phpQuery->documentWrapper->import($arg1)
+ );
+ } else {
+ /**
+ * Run CSS query:
+ * pq('div.myClass')
+ */
+ $phpQuery = new phpQueryObject($domId);
+ // if ($context && ($context instanceof PHPQUERY || is_subclass_of($context, 'phpQueryObject')))
+ if ($context && $context instanceof phpQueryObject)
+ $phpQuery->elements = $context->elements;
+ else if ($context && $context instanceof DOMNODELIST) {
+ $phpQuery->elements = array();
+ foreach ($context as $node)
+ $phpQuery->elements[] = $node;
+ } else if ($context && $context instanceof DOMNODE)
+ $phpQuery->elements = array($context);
+ return $phpQuery->find($arg1);
+ }
+ }
+ /**
+ * Sets default document to $id. Document has to be loaded prior
+ * to using this method.
+ * $id can be retrived via getDocumentID() or getDocumentIDRef().
+ *
+ * @param unknown_type $id
+ */
+ public static function selectDocument($id)
+ {
+ $id = self::getDocumentID($id);
+ self::debug("Selecting document '$id' as default one");
+ self::$defaultDocumentID = self::getDocumentID($id);
+ }
+ /**
+ * Returns document with id $id or last used as phpQueryObject.
+ * $id can be retrived via getDocumentID() or getDocumentIDRef().
+ * Chainable.
+ *
+ * @see phpQuery::selectDocument()
+ * @param unknown_type $id
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function getDocument($id = null)
+ {
+ if ($id)
+ phpQuery::selectDocument($id);
+ else
+ $id = phpQuery::$defaultDocumentID;
+ return new phpQueryObject($id);
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocument($markup = null, $contentType = null)
+ {
+ if (!$markup)
+ $markup = '';
+ $documentID = phpQuery::createDocumentWrapper($markup, $contentType);
+ return new phpQueryObject($documentID);
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentHTML($markup = null, $charset = null)
+ {
+ $contentType = $charset
+ ? ";charset=$charset"
+ : '';
+ return self::newDocument($markup, "text/html{$contentType}");
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentXML($markup = null, $charset = null)
+ {
+ $contentType = $charset
+ ? ";charset=$charset"
+ : '';
+ return self::newDocument($markup, "text/xml{$contentType}");
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentXHTML($markup = null, $charset = null)
+ {
+ $contentType = $charset
+ ? ";charset=$charset"
+ : '';
+ return self::newDocument($markup, "application/xhtml+xml{$contentType}");
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentPHP($markup = null, $contentType = "text/html")
+ {
+ // TODO pass charset to phpToMarkup if possible (use DOMDocumentWrapper function)
+ $markup = phpQuery::phpToMarkup($markup, self::$defaultCharset);
+ return self::newDocument($markup, $contentType);
+ }
+ public static function phpToMarkup($php, $charset = 'utf-8')
+ {
+ $regexes = array(
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)<' . '?php?(.*?)(?:\\?>)([^\']*)\'@s',
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)<' . '?php?(.*?)(?:\\?>)([^"]*)"@s',
+ );
+ foreach ($regexes as $regex)
+ while (preg_match($regex, $php, $matches)) {
+ $php = preg_replace_callback(
+ $regex,
+ // create_function('$m, $charset = "'.$charset.'"',
+ // 'return $m[1].$m[2]
+ // .htmlspecialchars("<"."?php".$m[4]."?".">", ENT_QUOTES|ENT_NOQUOTES, $charset)
+ // .$m[5].$m[2];'
+ // ),
+ array('phpQuery', '_phpToMarkupCallback'),
+ $php
+ );
+ }
+ $regex = '@(^|>[^<]*)+?(<\?php(.*?)(\?>))@s';
+ //preg_match_all($regex, $php, $matches);
+ //var_dump($matches);
+ $php = preg_replace($regex, '\\1 ', $php);
+ return $php;
+ }
+ public static function _phpToMarkupCallback($php, $charset = 'utf-8')
+ {
+ return $m[1] . $m[2]
+ . htmlspecialchars("<" . "?php" . $m[4] . "?" . ">", ENT_QUOTES | ENT_NOQUOTES, $charset)
+ . $m[5] . $m[2];
+ }
+ public static function _markupToPHPCallback($m)
+ {
+ return "<" . "?php " . htmlspecialchars_decode($m[1]) . " ?" . ">";
+ }
+ /**
+ * Converts document markup containing PHP code generated by phpQuery::php()
+ * into valid (executable) PHP code syntax.
+ *
+ * @param string|phpQueryObject $content
+ * @return string PHP code.
+ */
+ public static function markupToPHP($content)
+ {
+ if ($content instanceof phpQueryObject)
+ $content = $content->markupOuter();
+ /* ... to */
+ $content = preg_replace_callback(
+ '@\s*\s* @s',
+ // create_function('$m',
+ // 'return "<'.'?php ".htmlspecialchars_decode($m[1])." ?'.'>";'
+ // ),
+ array('phpQuery', '_markupToPHPCallback'),
+ $content
+ );
+ /* extra space added to save highlighters */
+ $regexes = array(
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(\')([^\']*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^\']*)\'@s',
+ '@(<(?!\\?)(?:[^>]|\\?>)+\\w+\\s*=\\s*)(")([^"]*)(?:<|%3C)\\?(?:php)?(.*?)(?:\\?(?:>|%3E))([^"]*)"@s',
+ );
+ foreach ($regexes as $regex)
+ while (preg_match($regex, $content))
+ $content = preg_replace_callback(
+ $regex,
+ function ($m) {
+ return $m[1] . $m[2] . $m[3] . "", " ", "\n", " ", "{", "$", "}", '"', "[", "]"),
+ htmlspecialchars_decode($m[4])
+ )
+ . " ?>" . $m[5] . $m[2];
+ },
+ $content
+ );
+ return $content;
+ }
+ /**
+ * Creates new document from file $file.
+ * Chainable.
+ *
+ * @param string $file URLs allowed. See File wrapper page at php.net for more supported sources.
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentFile($file, $contentType = null)
+ {
+ $documentID = self::createDocumentWrapper(
+ file_get_contents($file),
+ $contentType
+ );
+ return new phpQueryObject($documentID);
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentFileHTML($file, $charset = null)
+ {
+ $contentType = $charset
+ ? ";charset=$charset"
+ : '';
+ return self::newDocumentFile($file, "text/html{$contentType}");
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentFileXML($file, $charset = null)
+ {
+ $contentType = $charset
+ ? ";charset=$charset"
+ : '';
+ return self::newDocumentFile($file, "text/xml{$contentType}");
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentFileXHTML($file, $charset = null)
+ {
+ $contentType = $charset
+ ? ";charset=$charset"
+ : '';
+ return self::newDocumentFile($file, "application/xhtml+xml{$contentType}");
+ }
+ /**
+ * Creates new document from markup.
+ * Chainable.
+ *
+ * @param unknown_type $markup
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ */
+ public static function newDocumentFilePHP($file, $contentType = null)
+ {
+ return self::newDocumentPHP(file_get_contents($file), $contentType);
+ }
+ /**
+ * Reuses existing DOMDocument object.
+ * Chainable.
+ *
+ * @param $document DOMDocument
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @TODO support DOMDocument
+ */
+ public static function loadDocument($document)
+ {
+ // TODO
+ die('TODO loadDocument');
+ }
+ /**
+ * Enter description here...
+ *
+ * @param unknown_type $html
+ * @param unknown_type $domId
+ * @return unknown New DOM ID
+ * @todo support PHP tags in input
+ * @todo support passing DOMDocument object from self::loadDocument
+ */
+ protected static function createDocumentWrapper($html, $contentType = null, $documentID = null)
+ {
+ if (function_exists('domxml_open_mem'))
+ throw new Exception("Old PHP4 DOM XML extension detected. phpQuery won't work until this extension is enabled.");
+ // $id = $documentID
+ // ? $documentID
+ // : md5(microtime());
+ $document = null;
+ if ($html instanceof DOMDOCUMENT) {
+ if (self::getDocumentID($html)) {
+ // document already exists in phpQuery::$documents, make a copy
+ $document = clone $html;
+ } else {
+ // new document, add it to phpQuery::$documents
+ $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
+ }
+ } else {
+ $wrapper = new DOMDocumentWrapper($html, $contentType, $documentID);
+ }
+ // $wrapper->id = $id;
+ // bind document
+ phpQuery::$documents[$wrapper->id] = $wrapper;
+ // remember last loaded document
+ phpQuery::selectDocument($wrapper->id);
+ return $wrapper->id;
+ }
+ /**
+ * Extend class namespace.
+ *
+ * @param string|array $target
+ * @param array $source
+ * @TODO support string $source
+ * @return unknown_type
+ */
+ public static function extend($target, $source)
+ {
+ switch ($target) {
+ case 'phpQueryObject':
+ $targetRef = &self::$extendMethods;
+ $targetRef2 = &self::$pluginsMethods;
+ break;
+ case 'phpQuery':
+ $targetRef = &self::$extendStaticMethods;
+ $targetRef2 = &self::$pluginsStaticMethods;
+ break;
+ default:
+ throw new Exception("Unsupported \$target type");
+ }
+ if (is_string($source))
+ $source = array($source => $source);
+ foreach ($source as $method => $callback) {
+ if (isset($targetRef[$method])) {
+ // throw new Exception
+ self::debug("Duplicate method '{$method}', can\'t extend '{$target}'");
+ continue;
+ }
+ if (isset($targetRef2[$method])) {
+ // throw new Exception
+ self::debug("Duplicate method '{$method}' from plugin '{$targetRef2[$method]}',"
+ . " can\'t extend '{$target}'");
+ continue;
+ }
+ $targetRef[$method] = $callback;
+ }
+ return true;
+ }
+ /**
+ * Extend phpQuery with $class from $file.
+ *
+ * @param string $class Extending class name. Real class name can be prepended phpQuery_.
+ * @param string $file Filename to include. Defaults to "{$class}.php".
+ */
+ public static function plugin($class, $file = null)
+ {
+ // TODO $class checked agains phpQuery_$class
+ // if (strpos($class, 'phpQuery') === 0)
+ // $class = substr($class, 8);
+ if (in_array($class, self::$pluginsLoaded))
+ return true;
+ if (!$file)
+ $file = $class . '.php';
+ $objectClassExists = class_exists('phpQueryObjectPlugin_' . $class);
+ $staticClassExists = class_exists('phpQueryPlugin_' . $class);
+ if (!$objectClassExists && !$staticClassExists)
+ require_once($file);
+ self::$pluginsLoaded[] = $class;
+ // static methods
+ if (class_exists('phpQueryPlugin_' . $class)) {
+ $realClass = 'phpQueryPlugin_' . $class;
+ $vars = get_class_vars($realClass);
+ $loop = isset($vars['phpQueryMethods'])
+ && !is_null($vars['phpQueryMethods'])
+ ? $vars['phpQueryMethods']
+ : get_class_methods($realClass);
+ foreach ($loop as $method) {
+ if ($method == '__initialize')
+ continue;
+ if (!is_callable(array($realClass, $method)))
+ continue;
+ if (isset(self::$pluginsStaticMethods[$method])) {
+ throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '" . self::$pluginsStaticMethods[$method] . "'");
+ return;
+ }
+ self::$pluginsStaticMethods[$method] = $class;
+ }
+ if (method_exists($realClass, '__initialize'))
+ call_user_func_array(array($realClass, '__initialize'), array());
+ }
+ // object methods
+ if (class_exists('phpQueryObjectPlugin_' . $class)) {
+ $realClass = 'phpQueryObjectPlugin_' . $class;
+ $vars = get_class_vars($realClass);
+ $loop = isset($vars['phpQueryMethods'])
+ && !is_null($vars['phpQueryMethods'])
+ ? $vars['phpQueryMethods']
+ : get_class_methods($realClass);
+ foreach ($loop as $method) {
+ if (!is_callable(array($realClass, $method)))
+ continue;
+ if (isset(self::$pluginsMethods[$method])) {
+ throw new Exception("Duplicate method '{$method}' from plugin '{$c}' conflicts with same method from plugin '" . self::$pluginsMethods[$method] . "'");
+ continue;
+ }
+ self::$pluginsMethods[$method] = $class;
+ }
+ }
+ return true;
+ }
+ /**
+ * Unloades all or specified document from memory.
+ *
+ * @param mixed $documentID @see phpQuery::getDocumentID() for supported types.
+ */
+ public static function unloadDocuments($id = null)
+ {
+ if (isset($id)) {
+ if ($id = self::getDocumentID($id))
+ unset(phpQuery::$documents[$id]);
+ } else {
+ foreach (phpQuery::$documents as $k => $v) {
+ unset(phpQuery::$documents[$k]);
+ }
+ }
+ }
+ /**
+ * Parses phpQuery object or HTML result against PHP tags and makes them active.
+ *
+ * @param phpQuery|string $content
+ * @deprecated
+ * @return string
+ */
+ public static function unsafePHPTags($content)
+ {
+ return self::markupToPHP($content);
+ }
+ public static function DOMNodeListToArray($DOMNodeList)
+ {
+ $array = array();
+ if (!$DOMNodeList)
+ return $array;
+ foreach ($DOMNodeList as $node)
+ $array[] = $node;
+ return $array;
+ }
+ /**
+ * Checks if $input is HTML string, which has to start with '<'.
+ *
+ * @deprecated
+ * @param String $input
+ * @return Bool
+ * @todo still used ?
+ */
+ public static function isMarkup($input)
+ {
+ return !is_array($input) && substr(trim($input), 0, 1) == '<';
+ }
+ public static function debug($text)
+ {
+ if (self::$debug)
+ print var_dump($text);
+ }
+ /**
+ * Make an AJAX request.
+ *
+ * @param array See $options http://docs.jquery.com/Ajax/jQuery.ajax#toptions
+ * Additional options are:
+ * 'document' - document for global events, @see phpQuery::getDocumentID()
+ * 'referer' - implemented
+ * 'requested_with' - TODO; not implemented (X-Requested-With)
+ * @return Zend_Http_Client
+ * @link http://docs.jquery.com/Ajax/jQuery.ajax
+ *
+ * @TODO $options['cache']
+ * @TODO $options['processData']
+ * @TODO $options['xhr']
+ * @TODO $options['data'] as string
+ * @TODO XHR interface
+ */
+ public static function ajax($options = array(), $xhr = null)
+ {
+ $options = array_merge(
+ self::$ajaxSettings,
+ $options
+ );
+ $documentID = isset($options['document'])
+ ? self::getDocumentID($options['document'])
+ : null;
+ if ($xhr) {
+ // reuse existing XHR object, but clean it up
+ $client = $xhr;
+ // $client->setParameterPost(null);
+ // $client->setParameterGet(null);
+ $client->setAuth(false);
+ $client->setHeaders("If-Modified-Since", null);
+ $client->setHeaders("Referer", null);
+ $client->resetParameters();
+ } else {
+ // create new XHR object
+ require_once('Zend/Http/Client.php');
+ $client = new Zend_Http_Client();
+ $client->setCookieJar();
+ }
+ if (isset($options['timeout']))
+ $client->setConfig(array(
+ 'timeout' => $options['timeout'],
+ ));
+ // 'maxredirects' => 0,
+ foreach (self::$ajaxAllowedHosts as $k => $host)
+ if ($host == '.' && isset($_SERVER['HTTP_HOST']))
+ self::$ajaxAllowedHosts[$k] = $_SERVER['HTTP_HOST'];
+ $host = parse_url($options['url'], PHP_URL_HOST);
+ if (!in_array($host, self::$ajaxAllowedHosts)) {
+ throw new Exception("Request not permitted, host '$host' not present in "
+ . "phpQuery::\$ajaxAllowedHosts");
+ }
+ // JSONP
+ $jsre = "/=\\?(&|$)/";
+ if (isset($options['dataType']) && $options['dataType'] == 'jsonp') {
+ $jsonpCallbackParam = $options['jsonp']
+ ? $options['jsonp'] : 'callback';
+ if (strtolower($options['type']) == 'get') {
+ if (!preg_match($jsre, $options['url'])) {
+ $sep = strpos($options['url'], '?')
+ ? '&' : '?';
+ $options['url'] .= "$sep$jsonpCallbackParam=?";
+ }
+ } else if ($options['data']) {
+ $jsonp = false;
+ foreach ($options['data'] as $n => $v) {
+ if ($v == '?')
+ $jsonp = true;
+ }
+ if (!$jsonp) {
+ $options['data'][$jsonpCallbackParam] = '?';
+ }
+ }
+ $options['dataType'] = 'json';
+ }
+ if (isset($options['dataType']) && $options['dataType'] == 'json') {
+ $jsonpCallback = 'json_' . md5(microtime());
+ $jsonpData = $jsonpUrl = false;
+ if ($options['data']) {
+ foreach ($options['data'] as $n => $v) {
+ if ($v == '?')
+ $jsonpData = $n;
+ }
+ }
+ if (preg_match($jsre, $options['url']))
+ $jsonpUrl = true;
+ if ($jsonpData !== false || $jsonpUrl) {
+ // remember callback name for httpData()
+ $options['_jsonp'] = $jsonpCallback;
+ if ($jsonpData !== false)
+ $options['data'][$jsonpData] = $jsonpCallback;
+ if ($jsonpUrl)
+ $options['url'] = preg_replace($jsre, "=$jsonpCallback\\1", $options['url']);
+ }
+ }
+ $client->setUri($options['url']);
+ $client->setMethod(strtoupper($options['type']));
+ if (isset($options['referer']) && $options['referer'])
+ $client->setHeaders('Referer', $options['referer']);
+ $client->setHeaders(array(
+ // 'content-type' => $options['contentType'],
+ 'User-Agent' => 'Mozilla/5.0 (X11; U; Linux x86; en-US; rv:1.9.0.5) Gecko'
+ . '/2008122010 Firefox/3.0.5',
+ // TODO custom charset
+ 'Accept-Charset' => 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
+ // 'Connection' => 'keep-alive',
+ // 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+ 'Accept-Language' => 'en-us,en;q=0.5',
+ ));
+ if ($options['username'])
+ $client->setAuth($options['username'], $options['password']);
+ if (isset($options['ifModified']) && $options['ifModified'])
+ $client->setHeaders(
+ "If-Modified-Since",
+ self::$lastModified
+ ? self::$lastModified
+ : "Thu, 01 Jan 1970 00:00:00 GMT"
+ );
+ $client->setHeaders(
+ "Accept",
+ isset($options['dataType'])
+ && isset(self::$ajaxSettings['accepts'][$options['dataType']])
+ ? self::$ajaxSettings['accepts'][$options['dataType']] . ", */*"
+ : self::$ajaxSettings['accepts']['_default']
+ );
+ // TODO $options['processData']
+ if ($options['data'] instanceof phpQueryObject) {
+ $serialized = $options['data']->serializeArray($options['data']);
+ $options['data'] = array();
+ foreach ($serialized as $r)
+ $options['data'][$r['name']] = $r['value'];
+ }
+ if (strtolower($options['type']) == 'get') {
+ $client->setParameterGet($options['data']);
+ } else if (strtolower($options['type']) == 'post') {
+ $client->setEncType($options['contentType']);
+ $client->setParameterPost($options['data']);
+ }
+ if (self::$active == 0 && $options['global'])
+ phpQueryEvents::trigger($documentID, 'ajaxStart');
+ self::$active++;
+ // beforeSend callback
+ if (isset($options['beforeSend']) && $options['beforeSend'])
+ phpQuery::callbackRun($options['beforeSend'], array($client));
+ // ajaxSend event
+ if ($options['global'])
+ phpQueryEvents::trigger($documentID, 'ajaxSend', array($client, $options));
+ if (phpQuery::$debug) {
+ self::debug("{$options['type']}: {$options['url']}\n");
+ self::debug("Options: " . var_export($options, true) . " \n");
+ // if ($client->getCookieJar())
+ // self::debug("Cookies: ".var_export($client->getCookieJar()->getMatchingCookies($options['url']), true)." \n");
+ }
+ // request
+ $response = $client->request();
+ if (phpQuery::$debug) {
+ self::debug('Status: ' . $response->getStatus() . ' / ' . $response->getMessage());
+ self::debug($client->getLastRequest());
+ self::debug($response->getHeaders());
+ }
+ if ($response->isSuccessful()) {
+ // XXX tempolary
+ self::$lastModified = $response->getHeader('Last-Modified');
+ $data = self::httpData($response->getBody(), $options['dataType'], $options);
+ if (isset($options['success']) && $options['success'])
+ phpQuery::callbackRun($options['success'], array($data, $response->getStatus(), $options));
+ if ($options['global'])
+ phpQueryEvents::trigger($documentID, 'ajaxSuccess', array($client, $options));
+ } else {
+ if (isset($options['error']) && $options['error'])
+ phpQuery::callbackRun($options['error'], array($client, $response->getStatus(), $response->getMessage()));
+ if ($options['global'])
+ phpQueryEvents::trigger($documentID, 'ajaxError', array($client, /*$response->getStatus(),*/ $response->getMessage(), $options));
+ }
+ if (isset($options['complete']) && $options['complete'])
+ phpQuery::callbackRun($options['complete'], array($client, $response->getStatus()));
+ if ($options['global'])
+ phpQueryEvents::trigger($documentID, 'ajaxComplete', array($client, $options));
+ if ($options['global'] && !--self::$active)
+ phpQueryEvents::trigger($documentID, 'ajaxStop');
+ return $client;
+ // if (is_null($domId))
+ // $domId = self::$defaultDocumentID ? self::$defaultDocumentID : false;
+ // return new phpQueryAjaxResponse($response, $domId);
+ }
+ protected static function httpData($data, $type, $options)
+ {
+ if (isset($options['dataFilter']) && $options['dataFilter'])
+ $data = self::callbackRun($options['dataFilter'], array($data, $type));
+ if (is_string($data)) {
+ if ($type == "json") {
+ if (isset($options['_jsonp']) && $options['_jsonp']) {
+ $data = preg_replace('/^\s*\w+\((.*)\)\s*$/s', '$1', $data);
+ }
+ $data = self::parseJSON($data);
+ }
+ }
+ return $data;
+ }
+ /**
+ * Enter description here...
+ *
+ * @param array|phpQuery $data
+ *
+ */
+ public static function param($data)
+ {
+ return http_build_query($data, null, '&');
+ }
+ public static function get($url, $data = null, $callback = null, $type = null)
+ {
+ if (!is_array($data)) {
+ $callback = $data;
+ $data = null;
+ }
+ // TODO some array_values on this shit
+ return phpQuery::ajax(array(
+ 'type' => 'GET',
+ 'url' => $url,
+ 'data' => $data,
+ 'success' => $callback,
+ 'dataType' => $type,
+ ));
+ }
+ public static function post($url, $data = null, $callback = null, $type = null)
+ {
+ if (!is_array($data)) {
+ $callback = $data;
+ $data = null;
+ }
+ return phpQuery::ajax(array(
+ 'type' => 'POST',
+ 'url' => $url,
+ 'data' => $data,
+ 'success' => $callback,
+ 'dataType' => $type,
+ ));
+ }
+ public static function getJSON($url, $data = null, $callback = null)
+ {
+ if (!is_array($data)) {
+ $callback = $data;
+ $data = null;
+ }
+ // TODO some array_values on this shit
+ return phpQuery::ajax(array(
+ 'type' => 'GET',
+ 'url' => $url,
+ 'data' => $data,
+ 'success' => $callback,
+ 'dataType' => 'json',
+ ));
+ }
+ public static function ajaxSetup($options)
+ {
+ self::$ajaxSettings = array_merge(
+ self::$ajaxSettings,
+ $options
+ );
+ }
+ public static function ajaxAllowHost($host1, $host2 = null, $host3 = null)
+ {
+ $loop = is_array($host1)
+ ? $host1
+ : func_get_args();
+ foreach ($loop as $host) {
+ if ($host && !in_array($host, phpQuery::$ajaxAllowedHosts)) {
+ phpQuery::$ajaxAllowedHosts[] = $host;
+ }
+ }
+ }
+ public static function ajaxAllowURL($url1, $url2 = null, $url3 = null)
+ {
+ $loop = is_array($url1)
+ ? $url1
+ : func_get_args();
+ foreach ($loop as $url)
+ phpQuery::ajaxAllowHost(parse_url($url, PHP_URL_HOST));
+ }
+ /**
+ * Returns JSON representation of $data.
+ *
+ * @static
+ * @param mixed $data
+ * @return string
+ */
+ public static function toJSON($data)
+ {
+ if (function_exists('json_encode'))
+ return json_encode($data);
+ require_once('Zend/Json/Encoder.php');
+ return Zend_Json_Encoder::encode($data);
+ }
+ /**
+ * Parses JSON into proper PHP type.
+ *
+ * @static
+ * @param string $json
+ * @return mixed
+ */
+ public static function parseJSON($json)
+ {
+ if (function_exists('json_decode')) {
+ $return = json_decode(trim($json), true);
+ // json_decode and UTF8 issues
+ if (isset($return))
+ return $return;
+ }
+ require_once('Zend/Json/Decoder.php');
+ return Zend_Json_Decoder::decode($json);
+ }
+ /**
+ * Returns source's document ID.
+ *
+ * @param $source DOMNode|phpQueryObject
+ * @return string
+ */
+ public static function getDocumentID($source)
+ {
+ if ($source instanceof DOMDOCUMENT) {
+ foreach (phpQuery::$documents as $id => $document) {
+ if ($source->isSameNode($document->document))
+ return $id;
+ }
+ } else if ($source instanceof DOMNODE) {
+ foreach (phpQuery::$documents as $id => $document) {
+ if ($source->ownerDocument->isSameNode($document->document))
+ return $id;
+ }
+ } else if ($source instanceof phpQueryObject)
+ return $source->getDocumentID();
+ else if (is_string($source) && isset(phpQuery::$documents[$source]))
+ return $source;
+ }
+ /**
+ * Get DOMDocument object related to $source.
+ * Returns null if such document doesn't exist.
+ *
+ * @param $source DOMNode|phpQueryObject|string
+ * @return string
+ */
+ public static function getDOMDocument($source)
+ {
+ if ($source instanceof DOMDOCUMENT)
+ return $source;
+ $source = self::getDocumentID($source);
+ return $source
+ ? self::$documents[$id]['document']
+ : null;
+ }
+
+ // UTILITIES
+ // http://docs.jquery.com/Utilities
+
+ /**
+ *
+ * @return unknown_type
+ * @link http://docs.jquery.com/Utilities/jQuery.makeArray
+ */
+ public static function makeArray($obj)
+ {
+ $array = array();
+ if (is_object($object) && $object instanceof DOMNODELIST) {
+ foreach ($object as $value)
+ $array[] = $value;
+ } else if (is_object($object) && !($object instanceof Iterator)) {
+ foreach (get_object_vars($object) as $name => $value)
+ $array[0][$name] = $value;
+ } else {
+ foreach ($object as $name => $value)
+ $array[0][$name] = $value;
+ }
+ return $array;
+ }
+ public static function inArray($value, $array)
+ {
+ return in_array($value, $array);
+ }
+ /**
+ *
+ * @param $object
+ * @param $callback
+ * @return unknown_type
+ * @link http://docs.jquery.com/Utilities/jQuery.each
+ */
+ public static function each($object, $callback, $param1 = null, $param2 = null, $param3 = null)
+ {
+ $paramStructure = null;
+ if (func_num_args() > 2) {
+ $paramStructure = func_get_args();
+ $paramStructure = array_slice($paramStructure, 2);
+ }
+ if (is_object($object) && !($object instanceof Iterator)) {
+ foreach (get_object_vars($object) as $name => $value)
+ phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
+ } else {
+ foreach ($object as $name => $value)
+ phpQuery::callbackRun($callback, array($name, $value), $paramStructure);
+ }
+ }
+ /**
+ *
+ * @link http://docs.jquery.com/Utilities/jQuery.map
+ */
+ public static function map($array, $callback, $param1 = null, $param2 = null, $param3 = null)
+ {
+ $result = array();
+ $paramStructure = null;
+ if (func_num_args() > 2) {
+ $paramStructure = func_get_args();
+ $paramStructure = array_slice($paramStructure, 2);
+ }
+ foreach ($array as $v) {
+ $vv = phpQuery::callbackRun($callback, array($v), $paramStructure);
+ // $callbackArgs = $args;
+ // foreach($args as $i => $arg) {
+ // $callbackArgs[$i] = $arg instanceof CallbackParam
+ // ? $v
+ // : $arg;
+ // }
+ // $vv = call_user_func_array($callback, $callbackArgs);
+ if (is_array($vv)) {
+ foreach ($vv as $vvv)
+ $result[] = $vvv;
+ } else if ($vv !== null) {
+ $result[] = $vv;
+ }
+ }
+ return $result;
+ }
+ /**
+ *
+ * @param $callback Callback
+ * @param $params
+ * @param $paramStructure
+ * @return unknown_type
+ */
+ public static function callbackRun($callback, $params = array(), $paramStructure = null)
+ {
+ if (!$callback)
+ return;
+ if ($callback instanceof CallbackParameterToReference) {
+ // TODO support ParamStructure to select which $param push to reference
+ if (isset($params[0]))
+ $callback->callback = $params[0];
+ return true;
+ }
+ if ($callback instanceof Callback) {
+ $paramStructure = $callback->params;
+ $callback = $callback->callback;
+ }
+ if (!$paramStructure)
+ return call_user_func_array($callback, $params);
+ $p = 0;
+ foreach ($paramStructure as $i => $v) {
+ $paramStructure[$i] = $v instanceof CallbackParam
+ ? $params[$p++]
+ : $v;
+ }
+ return call_user_func_array($callback, $paramStructure);
+ }
+ /**
+ * Merge 2 phpQuery objects.
+ * @param array $one
+ * @param array $two
+ * @protected
+ * @todo node lists, phpQueryObject
+ */
+ public static function merge($one, $two)
+ {
+ $elements = $one->elements;
+ foreach ($two->elements as $node) {
+ $exists = false;
+ foreach ($elements as $node2) {
+ if ($node2->isSameNode($node))
+ $exists = true;
+ }
+ if (!$exists)
+ $elements[] = $node;
+ }
+ return $elements;
+ // $one = $one->newInstance();
+ // $one->elements = $elements;
+ // return $one;
+ }
+ /**
+ *
+ * @param $array
+ * @param $callback
+ * @param $invert
+ * @return unknown_type
+ * @link http://docs.jquery.com/Utilities/jQuery.grep
+ */
+ public static function grep($array, $callback, $invert = false)
+ {
+ $result = array();
+ foreach ($array as $k => $v) {
+ $r = call_user_func_array($callback, array($v, $k));
+ if ($r === !(bool)$invert)
+ $result[] = $v;
+ }
+ return $result;
+ }
+ public static function unique($array)
+ {
+ return array_unique($array);
+ }
+ /**
+ *
+ * @param $function
+ * @return unknown_type
+ * @TODO there are problems with non-static methods, second parameter pass it
+ * but doesnt verify is method is really callable
+ */
+ public static function isFunction($function)
+ {
+ return is_callable($function);
+ }
+ public static function trim($str)
+ {
+ return trim($str);
+ }
+ /* PLUGINS NAMESPACE */
+ /**
+ *
+ * @param $url
+ * @param $callback
+ * @param $param1
+ * @param $param2
+ * @param $param3
+ * @return phpQueryObject
+ */
+ public static function browserGet($url, $callback, $param1 = null, $param2 = null, $param3 = null)
+ {
+ if (self::plugin('WebBrowser')) {
+ $params = func_get_args();
+ return self::callbackRun(array(self::$plugins, 'browserGet'), $params);
+ } else {
+ self::debug('WebBrowser plugin not available...');
+ }
+ }
+ /**
+ *
+ * @param $url
+ * @param $data
+ * @param $callback
+ * @param $param1
+ * @param $param2
+ * @param $param3
+ * @return phpQueryObject
+ */
+ public static function browserPost($url, $data, $callback, $param1 = null, $param2 = null, $param3 = null)
+ {
+ if (self::plugin('WebBrowser')) {
+ $params = func_get_args();
+ return self::callbackRun(array(self::$plugins, 'browserPost'), $params);
+ } else {
+ self::debug('WebBrowser plugin not available...');
+ }
+ }
+ /**
+ *
+ * @param $ajaxSettings
+ * @param $callback
+ * @param $param1
+ * @param $param2
+ * @param $param3
+ * @return phpQueryObject
+ */
+ public static function browser($ajaxSettings, $callback, $param1 = null, $param2 = null, $param3 = null)
+ {
+ if (self::plugin('WebBrowser')) {
+ $params = func_get_args();
+ return self::callbackRun(array(self::$plugins, 'browser'), $params);
+ } else {
+ self::debug('WebBrowser plugin not available...');
+ }
+ }
+ /**
+ *
+ * @param $code
+ * @return string
+ */
+ public static function php($code)
+ {
+ return self::code('php', $code);
+ }
+ /**
+ *
+ * @param $type
+ * @param $code
+ * @return string
+ */
+ public static function code($type, $code)
+ {
+ return "<$type>$type>";
+ }
+
+ public static function __callStatic($method, $params)
+ {
+ return call_user_func_array(
+ array(phpQuery::$plugins, $method),
+ $params
+ );
+ }
+ protected static function dataSetupNode($node, $documentID)
+ {
+ // search are return if alredy exists
+ foreach (phpQuery::$documents[$documentID]->dataNodes as $dataNode) {
+ if ($node->isSameNode($dataNode))
+ return $dataNode;
+ }
+ // if doesn't, add it
+ phpQuery::$documents[$documentID]->dataNodes[] = $node;
+ return $node;
+ }
+ protected static function dataRemoveNode($node, $documentID)
+ {
+ // search are return if alredy exists
+ foreach (phpQuery::$documents[$documentID]->dataNodes as $k => $dataNode) {
+ if ($node->isSameNode($dataNode)) {
+ unset(self::$documents[$documentID]->dataNodes[$k]);
+ unset(self::$documents[$documentID]->data[$dataNode->dataID]);
+ }
+ }
+ }
+ public static function data($node, $name, $data, $documentID = null)
+ {
+ if (!$documentID)
+ // TODO check if this works
+ $documentID = self::getDocumentID($node);
+ $document = phpQuery::$documents[$documentID];
+ $node = self::dataSetupNode($node, $documentID);
+ if (!isset($node->dataID))
+ $node->dataID = ++phpQuery::$documents[$documentID]->uuid;
+ $id = $node->dataID;
+ if (!isset($document->data[$id]))
+ $document->data[$id] = array();
+ if (!is_null($data))
+ $document->data[$id][$name] = $data;
+ if ($name) {
+ if (isset($document->data[$id][$name]))
+ return $document->data[$id][$name];
+ } else
+ return $id;
+ }
+ public static function removeData($node, $name, $documentID)
+ {
+ if (!$documentID)
+ // TODO check if this works
+ $documentID = self::getDocumentID($node);
+ $document = phpQuery::$documents[$documentID];
+ $node = self::dataSetupNode($node, $documentID);
+ $id = $node->dataID;
+ if ($name) {
+ if (isset($document->data[$id][$name]))
+ unset($document->data[$id][$name]);
+ $name = null;
+ foreach ($document->data[$id] as $name)
+ break;
+ if (!$name)
+ self::removeData($node, $name, $documentID);
+ } else {
+ self::dataRemoveNode($node, $documentID);
+ }
+ }
+}
+/**
+ * Plugins static namespace class.
+ *
+ * @author Tobiasz Cudnik
+ * @package phpQuery
+ * @todo move plugin methods here (as statics)
+ */
+class phpQueryPlugins
+{
+ public function __call($method, $args)
+ {
+ if (isset(phpQuery::$extendStaticMethods[$method])) {
+ $return = call_user_func_array(
+ phpQuery::$extendStaticMethods[$method],
+ $args
+ );
+ } else if (isset(phpQuery::$pluginsStaticMethods[$method])) {
+ $class = phpQuery::$pluginsStaticMethods[$method];
+ $realClass = "phpQueryPlugin_$class";
+ $return = call_user_func_array(
+ array($realClass, $method),
+ $args
+ );
+ return isset($return)
+ ? $return
+ : $this;
+ } else
+ throw new Exception("Method '{$method}' doesnt exist");
+ }
+}
+/**
+ * Shortcut to phpQuery::pq($arg1, $context)
+ * Chainable.
+ *
+ * @see phpQuery::pq()
+ * @return phpQueryObject|QueryTemplatesSource|QueryTemplatesParse|QueryTemplatesSourceQuery
+ * @author Tobiasz Cudnik
+ * @package phpQuery
+ */
+function pq($arg1, $context = null)
+{
+ $args = func_get_args();
+ return call_user_func_array(
+ array('phpQuery', 'pq'),
+ $args
+ );
+}
+// add plugins dir and Zend framework to include path
+set_include_path(
+ get_include_path()
+ . PATH_SEPARATOR . dirname(__FILE__) . '/phpQuery/'
+ . PATH_SEPARATOR . dirname(__FILE__) . '/phpQuery/plugins/'
+);
+// why ? no __call nor __get for statics in php...
+// XXX __callStatic will be available in PHP 5.3
+phpQuery::$plugins = new phpQueryPlugins();
+// include bootstrap file (personal library config)
+if (file_exists(dirname(__FILE__) . '/phpQuery/bootstrap.php'))
+ require_once dirname(__FILE__) . '/phpQuery/bootstrap.php';
diff --git a/vendor/jaeger/querylist/.github/FUNDING.yml b/vendor/jaeger/querylist/.github/FUNDING.yml
new file mode 100644
index 0000000..c53fd16
--- /dev/null
+++ b/vendor/jaeger/querylist/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: querylist # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/vendor/jaeger/querylist/.gitignore b/vendor/jaeger/querylist/.gitignore
new file mode 100644
index 0000000..e2ee195
--- /dev/null
+++ b/vendor/jaeger/querylist/.gitignore
@@ -0,0 +1,5 @@
+/vendor/
+.idea/
+composer.lock
+.DS_Store
+*.cache
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/README-ZH.md b/vendor/jaeger/querylist/README-ZH.md
new file mode 100644
index 0000000..ec34971
--- /dev/null
+++ b/vendor/jaeger/querylist/README-ZH.md
@@ -0,0 +1,309 @@
+
+
+
+
+
+
+# QueryList 简介
+`QueryList`是一套简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。
+
+## 特性
+- 拥有与jQuery完全相同的CSS3 DOM选择器
+- 拥有与jQuery完全相同的DOM操作API
+- 拥有通用的列表采集方案
+- 拥有强大的HTTP请求套件,轻松实现如:模拟登陆、伪造浏览器、HTTP代理等意复杂的网络请求
+- 拥有乱码解决方案
+- 拥有强大的内容过滤功能,可使用jQuey选择器来过滤内容
+- 拥有高度的模块化设计,扩展性强
+- 拥有富有表现力的API
+- 拥有高质量文档
+- 拥有丰富的插件
+- 拥有专业的问答社区和交流群
+
+通过插件可以轻松实现诸如:
+- 多线程采集
+- 采集JavaScript动态渲染的页面 (PhantomJS/headless WebKit)
+- 图片本地化
+- 模拟浏览器行为,如:提交Form表单
+- 网络爬虫
+- .....
+
+## 环境要求
+- PHP >= 7.1
+
+> 如果你的PHP版本还停留在PHP5,或者不会使用Composer,你可以选择使用QueryList3,QueryList3支持php5.3以及手动安装。
+QueryList3 文档:http://v3.querylist.cc
+
+## 安装
+通过Composer安装:
+```
+composer require jaeger/querylist
+```
+
+## 使用
+
+#### 元素操作
+- 采集「昵图网」所有图片地址
+
+```php
+QueryList::get('http://www.nipic.com')->find('img')->attrs('src');
+```
+- 采集百度搜索结果
+
+```php
+$ql = QueryList::get('http://www.baidu.com/s?wd=QueryList');
+
+$ql->find('title')->text(); // 获取网站标题
+$ql->find('meta[name=keywords]')->content; // 获取网站头部关键词
+
+$ql->find('h3>a')->texts(); //获取搜索结果标题列表
+$ql->find('h3>a')->attrs('href'); //获取搜索结果链接列表
+
+$ql->find('img')->src; //获取第一张图片的链接地址
+$ql->find('img:eq(1)')->src; //获取第二张图片的链接地址
+$ql->find('img')->eq(2)->src; //获取第三张图片的链接地址
+// 遍历所有图片
+$ql->find('img')->map(function($img){
+ echo $img->alt; //打印图片的alt属性
+});
+```
+- 更多用法
+
+```php
+$ql->find('#head')->append('追加内容
')->find('div')->htmls();
+$ql->find('.two')->children('img')->attrs('alt'); //获取class为two元素下的所有img孩子节点
+//遍历class为two元素下的所有孩子节点
+$data = $ql->find('.two')->children()->map(function ($item){
+ //用is判断节点类型
+ if($item->is('a')){
+ return $item->text();
+ }elseif($item->is('img'))
+ {
+ return $item->alt;
+ }
+});
+
+$ql->find('a')->attr('href', 'newVal')->removeClass('className')->html('newHtml')->...
+$ql->find('div > p')->add('div > ul')->filter(':has(a)')->find('p:first')->nextAll()->andSelf()->...
+$ql->find('div.old')->replaceWith( $ql->find('div.new')->clone())->appendTo('.trash')->prepend('Deleted')->...
+```
+#### 列表采集
+采集百度搜索结果列表的标题和链接:
+```php
+$data = QueryList::get('http://www.baidu.com/s?wd=QueryList')
+ // 设置采集规则
+ ->rules([
+ 'title'=>array('h3','text'),
+ 'link'=>array('h3>a','href')
+ ])
+ ->query()->getData();
+
+print_r($data->all());
+```
+采集结果:
+```
+Array
+(
+ [0] => Array
+ (
+ [title] => QueryList|基于phpQuery的无比强大的PHP采集工具
+ [link] => http://www.baidu.com/link?url=GU_YbDT2IHk4ns1tjG2I8_vjmH0SCJEAPuuZN
+ )
+ [1] => Array
+ (
+ [title] => PHP 用QueryList抓取网页内容 - wb145230 - 博客园
+ [link] => http://www.baidu.com/link?url=zn0DXBnrvIF2ibRVW34KcRVFG1_bCdZvqvwIhUqiXaS
+ )
+ [2] => Array
+ (
+ [title] => 介绍- QueryList指导文档
+ [link] => http://www.baidu.com/link?url=pSypvMovqS4v2sWeQo5fDBJ4EoYhXYi0Lxx
+ )
+ //...
+)
+```
+#### 编码转换
+```php
+// 输出编码:UTF-8,输入编码:GB2312
+QueryList::get('https://top.etao.com')->encoding('UTF-8','GB2312')->find('a')->texts();
+
+// 输出编码:UTF-8,输入编码:自动识别
+QueryList::get('https://top.etao.com')->encoding('UTF-8')->find('a')->texts();
+```
+
+#### HTTP网络操作(GuzzleHttp)
+- 携带cookie登录新浪微博
+```php
+//采集新浪微博需要登录才能访问的页面
+$ql = QueryList::get('http://weibo.com','param1=testvalue & params2=somevalue',[
+ 'headers' => [
+ //填写从浏览器获取到的cookie
+ 'Cookie' => 'SINAGLOBAL=546064; wb_cmtLike_2112031=1; wvr=6;....'
+ ]
+]);
+//echo $ql->getHtml();
+echo $ql->find('title')->text();
+//输出: 我的首页 微博-随时随地发现新鲜事
+```
+- 使用Http代理
+```php
+$urlParams = ['param1' => 'testvalue','params2' => 'somevalue'];
+$opts = [
+ // 设置http代理
+ 'proxy' => 'http://222.141.11.17:8118',
+ //设置超时时间,单位:秒
+ 'timeout' => 30,
+ // 伪造http头
+ 'headers' => [
+ 'Referer' => 'https://querylist.cc/',
+ 'User-Agent' => 'testing/1.0',
+ 'Accept' => 'application/json',
+ 'X-Foo' => ['Bar', 'Baz'],
+ 'Cookie' => 'abc=111;xxx=222'
+ ]
+];
+$ql->get('http://httpbin.org/get',$urlParams,$opts);
+// echo $ql->getHtml();
+```
+
+- 模拟登录
+```php
+// 用post登录
+$ql = QueryList::post('http://xxxx.com/login',[
+ 'username' => 'admin',
+ 'password' => '123456'
+])->get('http://xxx.com/admin');
+//采集需要登录才能访问的页面
+$ql->get('http://xxx.com/admin/page');
+//echo $ql->getHtml();
+```
+
+#### Form表单操作
+模拟登陆GitHub
+```php
+// 获取QueryList实例
+$ql = QueryList::getInstance();
+//获取到登录表单
+$form = $ql->get('https://github.com/login')->find('form');
+
+//填写GitHub用户名和密码
+$form->find('input[name=login]')->val('your github username or email');
+$form->find('input[name=password]')->val('your github password');
+
+//序列化表单数据
+$fromData = $form->serializeArray();
+$postData = [];
+foreach ($fromData as $item) {
+ $postData[$item['name']] = $item['value'];
+}
+
+//提交登录表单
+$actionUrl = 'https://github.com'.$form->attr('action');
+$ql->post($actionUrl,$postData);
+//判断登录是否成功
+// echo $ql->getHtml();
+$userName = $ql->find('.header-nav-current-user>.css-truncate-target')->text();
+if($userName)
+{
+ echo '登录成功!欢迎你:'.$userName;
+}else{
+ echo '登录失败!';
+}
+```
+#### Bind功能扩展
+自定义扩展一个`myHttp`方法:
+```php
+$ql = QueryList::getInstance();
+
+//绑定一个myHttp方法到QueryList对象
+$ql->bind('myHttp',function ($url){
+ // $this 为当前的QueryList对象
+ $html = file_get_contents($url);
+ $this->setHtml($html);
+ return $this;
+});
+
+//然后就可以通过注册的名字来调用
+$data = $ql->myHttp('https://toutiao.io')->find('h3 a')->texts();
+print_r($data->all());
+```
+或者把实现体封装到class,然后这样绑定:
+```php
+$ql->bind('myHttp',function ($url){
+ return new MyHttp($this,$url);
+});
+```
+
+#### 插件使用
+- 使用PhantomJS插件采集JavaScript动态渲染的页面:
+
+```php
+// 安装时设置PhantomJS二进制文件路径
+$ql = QueryList::use(PhantomJs::class,'/usr/local/bin/phantomjs');
+
+// 采集今日头条手机版
+$data = $ql->browser('https://m.toutiao.com')->find('p')->texts();
+print_r($data->all());
+
+// 使用HTTP代理
+$ql->browser('https://m.toutiao.com',false,[
+ '--proxy' => '192.168.1.42:8080',
+ '--proxy-type' => 'http'
+])
+```
+
+- 使用CURL多线程插件,多线程采集GitHub排行榜:
+
+```php
+$ql = QueryList::use(CurlMulti::class);
+$ql->curlMulti([
+ 'https://github.com/trending/php',
+ 'https://github.com/trending/go',
+ //.....more urls
+])
+ // 每个任务成功完成调用此回调
+ ->success(function (QueryList $ql,CurlMulti $curl,$r){
+ echo "Current url:{$r['info']['url']} \r\n";
+ $data = $ql->find('h3 a')->texts();
+ print_r($data->all());
+})
+ // 每个任务失败回调
+->error(function ($errorInfo,CurlMulti $curl){
+ echo "Current url:{$errorInfo['info']['url']} \r\n";
+ print_r($errorInfo['error']);
+})
+->start([
+ // 最大并发数
+ 'maxThread' => 10,
+ // 错误重试次数
+ 'maxTry' => 3,
+]);
+
+```
+
+## 插件
+- [jae-jae/QueryList-PhantomJS](https://github.com/jae-jae/QueryList-PhantomJS): 使用PhantomJS采集JavaScript动态渲染的页面
+- [jae-jae/QueryList-CurlMulti](https://github.com/jae-jae/QueryList-CurlMulti) : Curl多线程采集
+- [jae-jae/QueryList-AbsoluteUrl](https://github.com/jae-jae/QueryList-AbsoluteUrl) : 转换URL相对路径到绝对路径
+- [jae-jae/QueryList-Rule-Google](https://github.com/jae-jae/QueryList-Rule-Google) : 谷歌搜索引擎
+- [jae-jae/QueryList-Rule-Baidu](https://github.com/jae-jae/QueryList-Rule-Baidu) : 百度搜索引擎
+
+
+查看更多的QueryList插件和基于QueryList的产品:[QueryList社区力量](https://github.com/jae-jae/QueryList-Community)
+
+## 贡献
+欢迎为QueryList贡献代码。关于贡献插件可以查看:[QueryList插件贡献说明](https://github.com/jae-jae/QueryList-Community/blob/master/CONTRIBUTING.md)
+
+## 寻求帮助?
+- QueryList主页: [http://querylist.cc](http://querylist.cc/)
+- QueryList文档: [http://doc.querylist.cc](http://doc.querylist.cc/)
+- QueryList问答:[http://wenda.querylist.cc](http://wenda.querylist.cc/)
+- QueryList交流QQ群:123266961
+- GitHub:https://github.com/jae-jae/QueryList
+- Git@OSC:http://git.oschina.net/jae/QueryList
+
+## Author
+Jaeger
+
+## Lisence
+QueryList is licensed under the license of MIT. See the LICENSE for more details.
diff --git a/vendor/jaeger/querylist/README.md b/vendor/jaeger/querylist/README.md
new file mode 100644
index 0000000..5ec559f
--- /dev/null
+++ b/vendor/jaeger/querylist/README.md
@@ -0,0 +1,304 @@
+
+
+
+
+
+
+# QueryList
+`QueryList` is a simple, elegant, extensible PHP Web Scraper (crawler/spider) ,based on phpQuery.
+
+[API Documentation](https://github.com/jae-jae/QueryList/wiki)
+
+[中文文档](README-ZH.md)
+
+## Features
+- Have the same CSS3 DOM selector as jQuery
+- Have the same DOM manipulation API as jQuery
+- Have a generic list crawling program
+- Have a strong HTTP request suite, easy to achieve such as: simulated landing, forged browser, HTTP proxy and other complex network requests
+- Have a messy code solution
+- Have powerful content filtering, you can use the jQuey selector to filter content
+- Has a high degree of modular design, scalability and strong
+- Have an expressive API
+- Has a wealth of plug-ins
+
+Through plug-ins you can easily implement things like:
+- Multithreaded crawl
+- Crawl JavaScript dynamic rendering page (PhantomJS/headless WebKit)
+- Image downloads to local
+- Simulate browser behavior such as submitting Form forms
+- Web crawler
+- .....
+
+## Requirements
+- PHP >= 7.1
+
+## Installation
+By Composer installation:
+```
+composer require jaeger/querylist
+```
+
+## Usage
+
+#### DOM Traversal and Manipulation
+- Crawl「GitHub」all picture links
+
+```php
+QueryList::get('https://github.com')->find('img')->attrs('src');
+```
+- Crawl Google search results
+
+```php
+$ql = QueryList::get('https://www.google.co.jp/search?q=QueryList');
+
+$ql->find('title')->text(); //The page title
+$ql->find('meta[name=keywords]')->content; //The page keywords
+
+$ql->find('h3>a')->texts(); //Get a list of search results titles
+$ql->find('h3>a')->attrs('href'); //Get a list of search results links
+
+$ql->find('img')->src; //Gets the link address of the first image
+$ql->find('img:eq(1)')->src; //Gets the link address of the second image
+$ql->find('img')->eq(2)->src; //Gets the link address of the third image
+// Loop all the images
+$ql->find('img')->map(function($img){
+ echo $img->alt; //Print the alt attribute of the image
+});
+```
+- More usage
+
+```php
+$ql->find('#head')->append('Append content
')->find('div')->htmls();
+$ql->find('.two')->children('img')->attrs('alt'); // Get the class is the "two" element under all img child nodes
+// Loop class is the "two" element under all child nodes
+$data = $ql->find('.two')->children()->map(function ($item){
+ // Use "is" to determine the node type
+ if($item->is('a')){
+ return $item->text();
+ }elseif($item->is('img'))
+ {
+ return $item->alt;
+ }
+});
+
+$ql->find('a')->attr('href', 'newVal')->removeClass('className')->html('newHtml')->...
+$ql->find('div > p')->add('div > ul')->filter(':has(a)')->find('p:first')->nextAll()->andSelf()->...
+$ql->find('div.old')->replaceWith( $ql->find('div.new')->clone())->appendTo('.trash')->prepend('Deleted')->...
+```
+#### List crawl
+Crawl the title and link of the Google search results list:
+```php
+$data = QueryList::get('https://www.google.co.jp/search?q=QueryList')
+ // Set the crawl rules
+ ->rules([
+ 'title'=>array('h3','text'),
+ 'link'=>array('h3>a','href')
+ ])
+ ->query()->getData();
+
+print_r($data->all());
+```
+ Results:
+```
+Array
+(
+ [0] => Array
+ (
+ [title] => Angular - QueryList
+ [link] => https://angular.io/api/core/QueryList
+ )
+ [1] => Array
+ (
+ [title] => QueryList | @angular/core - Angularリファレンス - Web Creative Park
+ [link] => http://www.webcreativepark.net/angular/querylist/
+ )
+ [2] => Array
+ (
+ [title] => QueryListにQueryを追加したり、追加されたことを感知する | TIPS ...
+ [link] => http://www.webcreativepark.net/angular/querylist_query_add_subscribe/
+ )
+ //...
+)
+```
+#### Encode convert
+```php
+// Out charset :UTF-8
+// In charset :GB2312
+QueryList::get('https://top.etao.com')->encoding('UTF-8','GB2312')->find('a')->texts();
+
+// Out charset:UTF-8
+// In charset:Automatic Identification
+QueryList::get('https://top.etao.com')->encoding('UTF-8')->find('a')->texts();
+```
+
+#### HTTP Client (GuzzleHttp)
+- Carry cookie login GitHub
+```php
+//Crawl GitHub content
+$ql = QueryList::get('https://github.com','param1=testvalue & params2=somevalue',[
+ 'headers' => [
+ // Fill in the cookie from the browser
+ 'Cookie' => 'SINAGLOBAL=546064; wb_cmtLike_2112031=1; wvr=6;....'
+ ]
+]);
+//echo $ql->getHtml();
+$userName = $ql->find('.header-nav-current-user>.css-truncate-target')->text();
+echo $userName;
+```
+- Use the Http proxy
+```php
+$urlParams = ['param1' => 'testvalue','params2' => 'somevalue'];
+$opts = [
+ // Set the http proxy
+ 'proxy' => 'http://222.141.11.17:8118',
+ //Set the timeout time in seconds
+ 'timeout' => 30,
+ // Fake HTTP headers
+ 'headers' => [
+ 'Referer' => 'https://querylist.cc/',
+ 'User-Agent' => 'testing/1.0',
+ 'Accept' => 'application/json',
+ 'X-Foo' => ['Bar', 'Baz'],
+ 'Cookie' => 'abc=111;xxx=222'
+ ]
+];
+$ql->get('http://httpbin.org/get',$urlParams,$opts);
+// echo $ql->getHtml();
+```
+
+- Analog login
+```php
+// Post login
+$ql = QueryList::post('http://xxxx.com/login',[
+ 'username' => 'admin',
+ 'password' => '123456'
+])->get('http://xxx.com/admin');
+// Crawl pages that need to be logged in to access
+$ql->get('http://xxx.com/admin/page');
+//echo $ql->getHtml();
+```
+
+#### Submit forms
+Login GitHub
+```php
+// Get the QueryList instance
+$ql = QueryList::getInstance();
+// Get the login form
+$form = $ql->get('https://github.com/login')->find('form');
+
+// Fill in the GitHub username and password
+$form->find('input[name=login]')->val('your github username or email');
+$form->find('input[name=password]')->val('your github password');
+
+// Serialize the form data
+$fromData = $form->serializeArray();
+$postData = [];
+foreach ($fromData as $item) {
+ $postData[$item['name']] = $item['value'];
+}
+
+// Submit the login form
+$actionUrl = 'https://github.com'.$form->attr('action');
+$ql->post($actionUrl,$postData);
+// To determine whether the login is successful
+// echo $ql->getHtml();
+$userName = $ql->find('.header-nav-current-user>.css-truncate-target')->text();
+if($userName)
+{
+ echo 'Login successful ! Welcome:'.$userName;
+}else{
+ echo 'Login failed !';
+}
+```
+#### Bind function extension
+Customize the extension of a `myHttp` method:
+```php
+$ql = QueryList::getInstance();
+
+//Bind a `myHttp` method to the QueryList object
+$ql->bind('myHttp',function ($url){
+ // $this is the current QueryList object
+ $html = file_get_contents($url);
+ $this->setHtml($html);
+ return $this;
+});
+
+// And then you can call by the name of the binding
+$data = $ql->myHttp('https://toutiao.io')->find('h3 a')->texts();
+print_r($data->all());
+```
+Or package to class, and then bind:
+```php
+$ql->bind('myHttp',function ($url){
+ return new MyHttp($this,$url);
+});
+```
+
+#### Plugin used
+- Use the PhantomJS plugin to crawl JavaScript dynamically rendered pages:
+
+```php
+// Set the PhantomJS binary file path during installation
+$ql = QueryList::use(PhantomJs::class,'/usr/local/bin/phantomjs');
+
+// Crawl「500px」all picture links
+$data = $ql->browser('https://500px.com/editors')->find('img')->attrs('src');
+print_r($data->all());
+
+// Use the HTTP proxy
+$ql->browser('https://500px.com/editors',false,[
+ '--proxy' => '192.168.1.42:8080',
+ '--proxy-type' => 'http'
+])
+```
+
+- Using the CURL multithreading plug-in, multi-threaded crawling GitHub trending :
+
+```php
+$ql = QueryList::use(CurlMulti::class);
+$ql->curlMulti([
+ 'https://github.com/trending/php',
+ 'https://github.com/trending/go',
+ //.....more urls
+])
+ // Called if task is success
+ ->success(function (QueryList $ql,CurlMulti $curl,$r){
+ echo "Current url:{$r['info']['url']} \r\n";
+ $data = $ql->find('h3 a')->texts();
+ print_r($data->all());
+})
+ // Task fail callback
+->error(function ($errorInfo,CurlMulti $curl){
+ echo "Current url:{$errorInfo['info']['url']} \r\n";
+ print_r($errorInfo['error']);
+})
+->start([
+ // Maximum number of threads
+ 'maxThread' => 10,
+ // Number of error retries
+ 'maxTry' => 3,
+]);
+
+```
+
+## Plugins
+- [jae-jae/QueryList-PhantomJS](https://github.com/jae-jae/QueryList-PhantomJS):Use PhantomJS to crawl Javascript dynamically rendered page.
+- [jae-jae/QueryList-CurlMulti](https://github.com/jae-jae/QueryList-CurlMulti) : Curl multi threading.
+- [jae-jae/QueryList-AbsoluteUrl](https://github.com/jae-jae/QueryList-AbsoluteUrl) : Converting relative urls to absolute.
+- [jae-jae/QueryList-Rule-Google](https://github.com/jae-jae/QueryList-Rule-Google) : Google searcher.
+- [jae-jae/QueryList-Rule-Baidu](https://github.com/jae-jae/QueryList-Rule-Baidu) : Baidu searcher.
+
+
+View more QueryList plugins and QueryList-based products: [QueryList Community](https://github.com/jae-jae/QueryList-Community)
+
+## Contributing
+Welcome to contribute code for the QueryList。About Contributing Plugins can be viewed:[QueryList Plugin Contributing Guide](https://github.com/jae-jae/QueryList-Community/blob/master/CONTRIBUTING.md)
+
+## Author
+Jaeger
+
+If this library is useful for you, say thanks [buying me a beer :beer:](https://www.paypal.me/jaepay)!
+
+## Lisence
+QueryList is licensed under the license of MIT. See the LICENSE for more details.
diff --git a/vendor/jaeger/querylist/composer.json b/vendor/jaeger/querylist/composer.json
new file mode 100644
index 0000000..e1bcefe
--- /dev/null
+++ b/vendor/jaeger/querylist/composer.json
@@ -0,0 +1,40 @@
+{
+ "name": "jaeger/querylist",
+ "description": "Simple, elegant, extensible PHP Web Scraper (crawler/spider),Use the css3 dom selector,Based on phpQuery! 简洁、优雅、可扩展的PHP采集工具(爬虫),基于phpQuery。",
+ "keywords":["QueryList","phpQuery","spider"],
+ "homepage": "http://querylist.cc",
+ "require": {
+ "PHP":">=7.1",
+ "jaeger/phpquery-single": "^1",
+ "jaeger/g-http": "^1.1",
+ "ext-dom": "*",
+ "tightenco/collect": ">5.0"
+ },
+ "suggest":{
+
+ },
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Jaeger",
+ "email": "JaegerCode@gmail.com"
+ }
+ ],
+ "autoload":{
+ "psr-4":{
+ "QL\\":"src"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests/"
+ }
+ },
+ "require-dev": {
+ "symfony/var-dumper": "^3.3",
+ "phpunit/phpunit": "^8.5"
+ },
+ "scripts": {
+ "test": "./vendor/bin/phpunit"
+ }
+}
diff --git a/vendor/jaeger/querylist/logo.png b/vendor/jaeger/querylist/logo.png
new file mode 100644
index 0000000..a29b3e6
Binary files /dev/null and b/vendor/jaeger/querylist/logo.png differ
diff --git a/vendor/jaeger/querylist/phpunit.xml b/vendor/jaeger/querylist/phpunit.xml
new file mode 100644
index 0000000..ba72a84
--- /dev/null
+++ b/vendor/jaeger/querylist/phpunit.xml
@@ -0,0 +1,19 @@
+
+
+
+ ./tests
+
+
+
+
+
+ src
+
+
+
+
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Config.php b/vendor/jaeger/querylist/src/Config.php
new file mode 100644
index 0000000..b2ababb
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Config.php
@@ -0,0 +1,94 @@
+
+ * Date: 2017/9/22
+ */
+
+namespace QL;
+use Closure;
+use Tightenco\Collect\Support\Collection;
+
+class Config
+{
+ protected static $instance = null;
+
+ protected $plugins;
+ protected $binds;
+
+ /**
+ * Config constructor.
+ */
+ public function __construct()
+ {
+ $this->plugins = new Collection();
+ $this->binds = new Collection();
+ }
+
+
+ /**
+ * Get the Config instance
+ *
+ * @return null|Config
+ */
+ public static function getInstance()
+ {
+ self::$instance || self::$instance = new self();
+ return self::$instance;
+ }
+
+ /**
+ * Global installation plugin
+ *
+ * @param $plugins
+ * @param array ...$opt
+ * @return $this
+ */
+ public function use($plugins,...$opt)
+ {
+ if(is_string($plugins)){
+ $this->plugins->push([$plugins,$opt]);
+ }else{
+ $this->plugins = $this->plugins->merge($plugins);
+ }
+ return $this;
+ }
+
+ /**
+ * Global binding custom method
+ *
+ * @param string $name
+ * @param Closure $provider
+ * @return $this
+ */
+ public function bind(string $name, Closure $provider)
+ {
+ $this->binds[$name] = $provider;
+ return $this;
+ }
+
+ public function bootstrap(QueryList $queryList)
+ {
+ $this->installPlugins($queryList);
+ $this->installBind($queryList);
+ }
+
+ protected function installPlugins(QueryList $queryList)
+ {
+ $this->plugins->each(function($plugin) use($queryList){
+ if(is_string($plugin)){
+ $queryList->use($plugin);
+ }else{
+ $queryList->use($plugin[0],...$plugin[1]);
+ }
+ });
+ }
+
+ protected function installBind(QueryList $queryList)
+ {
+ $this->binds->each(function ($provider,$name) use($queryList){
+ $queryList->bind($name,$provider);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Contracts/PluginContract.php b/vendor/jaeger/querylist/src/Contracts/PluginContract.php
new file mode 100644
index 0000000..8bbd498
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Contracts/PluginContract.php
@@ -0,0 +1,15 @@
+
+ * Date: 2017/9/22
+ */
+
+namespace QL\Contracts;
+
+use QL\QueryList;
+
+interface PluginContract
+{
+ public static function install(QueryList $queryList,...$opt);
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Contracts/ServiceProviderContract.php b/vendor/jaeger/querylist/src/Contracts/ServiceProviderContract.php
new file mode 100644
index 0000000..1bdf28b
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Contracts/ServiceProviderContract.php
@@ -0,0 +1,15 @@
+
+ * Date: 2017/9/20
+ */
+
+namespace QL\Contracts;
+
+use QL\Kernel;
+
+interface ServiceProviderContract
+{
+ public function register(Kernel $kernel);
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Dom/Dom.php b/vendor/jaeger/querylist/src/Dom/Dom.php
new file mode 100644
index 0000000..38f71a1
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Dom/Dom.php
@@ -0,0 +1,30 @@
+
+ * Date: 2017/9/19
+ */
+
+namespace QL\Dom;
+
+use phpQueryObject;
+
+class Dom
+{
+
+ protected $document;
+
+ /**
+ * Dom constructor.
+ */
+ public function __construct(phpQueryObject $document)
+ {
+ $this->document = $document;
+ }
+
+ public function find($selector)
+ {
+ $elements = $this->document->find($selector);
+ return new Elements($elements);
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Dom/Elements.php b/vendor/jaeger/querylist/src/Dom/Elements.php
new file mode 100644
index 0000000..943e3ce
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Dom/Elements.php
@@ -0,0 +1,260 @@
+
+ * Date: 2017/9/19
+ */
+
+namespace QL\Dom;
+
+use phpDocumentor\Reflection\Types\Null_;
+use phpQueryObject;
+use Tightenco\Collect\Support\Collection;
+
+/**
+ * Class Elements
+ * @package QL\Dom
+ *
+ * @method Elements toReference(&$var)
+ * @method Elements documentFragment($state = null)
+ * @method Elements toRoot()
+ * @method Elements getDocumentIDRef(&$documentID)
+ * @method Elements getDocument()
+ * @method \DOMDocument getDOMDocument()
+ * @method Elements getDocumentID()
+ * @method Elements unloadDocument()
+ * @method bool isHTML()
+ * @method bool isXHTML()
+ * @method bool isXML()
+ * @method string serialize()
+ * @method array serializeArray($submit = null)
+ * @method \DOMElement|\DOMElement[] get($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ * @method string|array getString($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ * @method string|array getStrings($index = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ * @method Elements newInstance($newStack = null)
+ * @method Elements find($selectors, $context = null, $noHistory = false)
+ * @method Elements|bool is($selector, $nodes = null)
+ * @method Elements filterCallback($callback, $_skipHistory = false)
+ * @method Elements filter($selectors, $_skipHistory = false)
+ * @method Elements load($url, $data = null, $callback = null)
+ * @method Elements trigger($type, $data = [])
+ * @method Elements triggerHandler($type, $data = [])
+ * @method Elements bind($type, $data, $callback = null)
+ * @method Elements unbind($type = null, $callback = null)
+ * @method Elements change($callback = null)
+ * @method Elements submit($callback = null)
+ * @method Elements click($callback = null)
+ * @method Elements wrapAllOld($wrapper)
+ * @method Elements wrapAll($wrapper)
+ * @method Elements wrapAllPHP($codeBefore, $codeAfter)
+ * @method Elements wrap($wrapper)
+ * @method Elements wrapPHP($codeBefore, $codeAfter)
+ * @method Elements wrapInner($wrapper)
+ * @method Elements wrapInnerPHP($codeBefore, $codeAfter)
+ * @method Elements contents()
+ * @method Elements contentsUnwrap()
+ * @method Elements switchWith($markup)
+ * @method Elements eq($num)
+ * @method Elements size()
+ * @method Elements length()
+ * @method int count()
+ * @method Elements end($level = 1)
+ * @method Elements _clone()
+ * @method Elements replaceWithPHP($code)
+ * @method Elements replaceWith($content)
+ * @method Elements replaceAll($selector)
+ * @method Elements remove($selector = null)
+ * @method Elements|string markup($markup = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ * @method string markupOuter($callback1 = null, $callback2 = null, $callback3 = null)
+ * @method Elements|string html($html = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ * @method Elements|string xml($xml = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ * @method string htmlOuter($callback1 = null, $callback2 = null, $callback3 = null)
+ * @method string xmlOuter($callback1 = null, $callback2 = null, $callback3 = null)
+ * @method Elements php($code)
+ * @method string markupPHP($code)
+ * @method string markupOuterPHP()
+ * @method Elements children($selector)
+ * @method Elements ancestors($selector)
+ * @method Elements append($content)
+ * @method Elements appendPHP($content)
+ * @method Elements appendTo($seletor)
+ * @method Elements prepend($content)
+ * @method Elements prependPHP($content)
+ * @method Elements prependTo($seletor)
+ * @method Elements before($content)
+ * @method Elements beforePHP($content)
+ * @method Elements insertBefore($seletor)
+ * @method Elements after($content)
+ * @method Elements afterPHP($content)
+ * @method Elements insertAfter($seletor)
+ * @method Elements insert($target, $type)
+ * @method int index($subject)
+ * @method Elements slice($start, $end = null)
+ * @method Elements reverse()
+ * @method Elements|string text($text = null, $callback1 = null, $callback2 = null, $callback3 = null)
+ * @method Elements plugin($class, $file = null)
+ * @method Elements _next($selector = null)
+ * @method Elements _prev($selector = null)
+ * @method Elements prev($selector = null)
+ * @method Elements prevAll($selector = null)
+ * @method Elements nextAll($selector = null)
+ * @method Elements siblings($selector = null)
+ * @method Elements not($selector = null)
+ * @method Elements add($selector = null)
+ * @method Elements parent($selector = null)
+ * @method Elements parents($selector = null)
+ * @method Elements stack($nodeTypes = null)
+ * @method Elements|string attr($attr = null, $value = null)
+ * @method Elements attrPHP($attr, $code)
+ * @method Elements removeAttr($attr)
+ * @method Elements|string val($val = null)
+ * @method Elements andSelf()
+ * @method Elements addClass($className)
+ * @method Elements addClassPHP($className)
+ * @method bool hasClass($className)
+ * @method Elements removeClass($className)
+ * @method Elements toggleClass($className)
+ * @method Elements _empty()
+ * @method Elements callback($callback, $param1 = null, $param2 = null, $param3 = null)
+ * @method string data($key, $value = null)
+ * @method Elements removeData($key)
+ * @method void rewind()
+ * @method Elements current()
+ * @method int key()
+ * @method Elements next($cssSelector = null)
+ * @method bool valid()
+ * @method bool offsetExists($offset)
+ * @method Elements offsetGet($offset)
+ * @method void offsetSet($offset, $value)
+ * @method string whois($oneNode)
+ * @method Elements dump()
+ * @method Elements dumpWhois()
+ * @method Elements dumpLength()
+ * @method Elements dumpTree($html, $title)
+ * @method dumpDie()
+ */
+class Elements
+{
+ /**
+ * @var phpQueryObject
+ */
+ protected $elements;
+
+ /**
+ * Elements constructor.
+ * @param $elements
+ */
+ public function __construct(phpQueryObject $elements)
+ {
+ $this->elements = $elements;
+ }
+
+ public function __get($name)
+ {
+ return property_exists($this->elements, $name) ? $this->elements->$name : $this->elements->attr($name);
+ }
+
+ public function __call($name, $arguments)
+ {
+ $obj = call_user_func_array([$this->elements, $name], $arguments);
+ if ($obj instanceof phpQueryObject) {
+ $obj = new self($obj);
+ } else if (is_string($obj)) {
+ $obj = trim($obj);
+ }
+ return $obj;
+ }
+
+ /**
+ * Iterating elements
+ *
+ * @param callable $callback
+ *
+ * @return $this
+ */
+ public function each(callable $callback)
+ {
+ foreach ($this->elements as $key => $element) {
+ $break = $callback(new self(pq($element)), $key);
+ if ($break === false) {
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Iterating elements
+ *
+ * @param $callback
+ * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection
+ */
+ public function map($callback)
+ {
+ $collection = new Collection();
+ $this->elements->each(function ($dom) use (& $collection, $callback) {
+ $collection->push($callback(new self(pq($dom))));
+ });
+ return $collection;
+ }
+
+ /**
+ * Gets the attributes of all the elements
+ *
+ * @param string $attr HTML attribute name
+ * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection
+ */
+ public function attrs($attr)
+ {
+ return $this->map(function ($item) use ($attr) {
+ return $item->attr($attr);
+ });
+ }
+
+ /**
+ * Gets the text of all the elements
+ *
+ * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection
+ */
+ public function texts()
+ {
+ return $this->map(function ($item) {
+ return trim($item->text());
+ });
+ }
+
+ /**
+ * Gets the html of all the elements
+ *
+ * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection
+ */
+ public function htmls()
+ {
+ return $this->map(function ($item) {
+ return trim($item->html());
+ });
+ }
+
+ /**
+ * Gets the htmlOuter of all the elements
+ *
+ * @return \Illuminate\Support\Collection|\Tightenco\Collect\Support\Collection
+ */
+ public function htmlOuters()
+ {
+ return $this->map(function ($item) {
+ return trim($item->htmlOuter());
+ });
+ }
+
+
+ /**
+ * @return phpQueryObject
+ */
+ public function getElements(): phpQueryObject
+ {
+ return $this->elements;
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Dom/Query.php b/vendor/jaeger/querylist/src/Dom/Query.php
new file mode 100644
index 0000000..869a0cc
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Dom/Query.php
@@ -0,0 +1,322 @@
+
+ * Date: 2017/9/21
+ */
+
+namespace QL\Dom;
+
+use Tightenco\Collect\Support\Collection;
+use phpQuery;
+use phpQueryObject;
+use QL\QueryList;
+use Closure;
+
+class Query
+{
+ protected $html;
+ /**
+ * @var \phpQueryObject
+ */
+ protected $document;
+ protected $rules;
+ protected $range = null;
+ protected $ql;
+ /**
+ * @var Collection
+ */
+ protected $data;
+
+
+ public function __construct(QueryList $ql)
+ {
+ $this->ql = $ql;
+ }
+
+ /**
+ * @param bool $rel
+ * @return String
+ */
+ public function getHtml($rel = true)
+ {
+ return $rel ? $this->document->htmlOuter() : $this->html;
+ }
+
+ /**
+ * @param $html
+ * @param null $charset
+ * @return QueryList
+ */
+ public function setHtml($html, $charset = null)
+ {
+ $this->html = value($html);
+ $this->destroyDocument();
+ $this->document = phpQuery::newDocumentHTML($this->html, $charset);
+ return $this->ql;
+ }
+
+ /**
+ * Get crawl results
+ *
+ * @param Closure|null $callback
+ * @return Collection|static
+ */
+ public function getData(Closure $callback = null)
+ {
+ return $this->handleData($this->data, $callback);
+ }
+
+ /**
+ * @param Collection $data
+ */
+ public function setData(Collection $data)
+ {
+ $this->data = $data;
+ }
+
+
+ /**
+ * Searches for all elements that match the specified expression.
+ *
+ * @param $selector A string containing a selector expression to match elements against.
+ * @return Elements
+ */
+ public function find($selector)
+ {
+ return (new Dom($this->document))->find($selector);
+ }
+
+ /**
+ * Set crawl rule
+ *
+ * $rules = [
+ * 'rule_name1' => ['selector','HTML attribute | text | html','Tag filter list','callback'],
+ * 'rule_name2' => ['selector','HTML attribute | text | html','Tag filter list','callback'],
+ * // ...
+ * ]
+ *
+ * @param array $rules
+ * @return QueryList
+ */
+ public function rules(array $rules)
+ {
+ $this->rules = $rules;
+ return $this->ql;
+ }
+
+
+ /**
+ * Set the slice area for crawl list
+ *
+ * @param $selector
+ * @return QueryList
+ */
+ public function range($selector)
+ {
+ $this->range = $selector;
+ return $this->ql;
+ }
+
+ /**
+ * Remove HTML head,try to solve the garbled
+ *
+ * @return QueryList
+ */
+ public function removeHead()
+ {
+ $html = preg_replace('/(|).+<\/head>/is', '', $this->html);
+ $this->setHtml($html);
+ return $this->ql;
+ }
+
+ /**
+ * Execute the query rule
+ *
+ * @param Closure|null $callback
+ * @return QueryList
+ */
+ public function query(Closure $callback = null)
+ {
+ $this->data = $this->getList();
+ $this->data = $this->handleData($this->data, $callback);
+ return $this->ql;
+ }
+
+ public function handleData(Collection $data, $callback)
+ {
+ if (is_callable($callback)) {
+ if (empty($this->range)) {
+ $data = new Collection($callback($data->all(), null));
+ } else {
+ $data = $data->map($callback);
+ }
+ }
+
+ return $data;
+ }
+
+ protected function getList()
+ {
+ $data = [];
+ if (empty($this->range)) {
+ foreach ($this->rules as $key => $reg_value) {
+ $rule = $this->parseRule($reg_value);
+ $contentElements = $this->document->find($rule['selector']);
+ $data[$key] = $this->extractContent($contentElements, $key, $rule);
+ }
+ } else {
+ $rangeElements = $this->document->find($this->range);
+ $i = 0;
+ foreach ($rangeElements as $element) {
+ foreach ($this->rules as $key => $reg_value) {
+ $rule = $this->parseRule($reg_value);
+ $contentElements = pq($element)->find($rule['selector']);
+ $data[$i][$key] = $this->extractContent($contentElements, $key, $rule);
+ }
+ $i++;
+ }
+ }
+
+ return new Collection($data);
+ }
+
+ protected function extractContent(phpQueryObject $pqObj, $ruleName, $rule)
+ {
+ switch ($rule['attr']) {
+ case 'text':
+ $content = $this->allowTags($pqObj->html(), $rule['filter_tags']);
+ break;
+ case 'texts':
+ $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) {
+ return $this->allowTags($element->html(), $rule['filter_tags']);
+ })->all();
+ break;
+ case 'html':
+ $content = $this->stripTags($pqObj->html(), $rule['filter_tags']);
+ break;
+ case 'htmls':
+ $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) {
+ return $this->stripTags($element->html(), $rule['filter_tags']);
+ })->all();
+ break;
+ case 'htmlOuter':
+ $content = $this->stripTags($pqObj->htmlOuter(), $rule['filter_tags']);
+ break;
+ case 'htmlOuters':
+ $content = (new Elements($pqObj))->map(function (Elements $element) use ($rule) {
+ return $this->stripTags($element->htmlOuter(), $rule['filter_tags']);
+ })->all();
+ break;
+ default:
+ if(preg_match('/attr\((.+)\)/', $rule['attr'], $arr)) {
+ $content = $pqObj->attr($arr[1]);
+ } elseif (preg_match('/attrs\((.+)\)/', $rule['attr'], $arr)) {
+ $content = (new Elements($pqObj))->attrs($arr[1])->all();
+ } else {
+ $content = $pqObj->attr($rule['attr']);
+ }
+ break;
+ }
+
+ if (is_callable($rule['handle_callback'])) {
+ $content = call_user_func($rule['handle_callback'], $content, $ruleName);
+ }
+
+ return $content;
+ }
+
+ protected function parseRule($rule)
+ {
+ $result = [];
+ $result['selector'] = $rule[0];
+ $result['attr'] = $rule[1];
+ $result['filter_tags'] = $rule[2] ?? '';
+ $result['handle_callback'] = $rule[3] ?? null;
+
+ return $result;
+ }
+
+ /**
+ * 去除特定的html标签
+ * @param string $html
+ * @param string $tags_str 多个标签名之间用空格隔开
+ * @return string
+ */
+ protected function stripTags($html, $tags_str)
+ {
+ $tagsArr = $this->tag($tags_str);
+ $html = $this->removeTags($html, $tagsArr[1]);
+ $p = array();
+ foreach ($tagsArr[0] as $tag) {
+ $p[] = "/(<(?:\/" . $tag . "|" . $tag . ")[^>]*>)/i";
+ }
+ $html = preg_replace($p, "", trim($html));
+ return $html;
+ }
+
+ /**
+ * 保留特定的html标签
+ * @param string $html
+ * @param string $tags_str 多个标签名之间用空格隔开
+ * @return string
+ */
+ protected function allowTags($html, $tags_str)
+ {
+ $tagsArr = $this->tag($tags_str);
+ $html = $this->removeTags($html, $tagsArr[1]);
+ $allow = '';
+ foreach ($tagsArr[0] as $tag) {
+ $allow .= "<$tag> ";
+ }
+ return strip_tags(trim($html), $allow);
+ }
+
+ protected function tag($tags_str)
+ {
+ $tagArr = preg_split("/\s+/", $tags_str, -1, PREG_SPLIT_NO_EMPTY);
+ $tags = array(array(), array());
+ foreach ($tagArr as $tag) {
+ if (preg_match('/-(.+)/', $tag, $arr)) {
+ array_push($tags[1], $arr[1]);
+ } else {
+ array_push($tags[0], $tag);
+ }
+ }
+ return $tags;
+ }
+
+ /**
+ * 移除特定的html标签
+ * @param string $html
+ * @param array $tags 标签数组
+ * @return string
+ */
+ protected function removeTags($html, $tags)
+ {
+ $tag_str = '';
+ if (count($tags)) {
+ foreach ($tags as $tag) {
+ $tag_str .= $tag_str ? ',' . $tag : $tag;
+ }
+// phpQuery::$defaultCharset = $this->inputEncoding?$this->inputEncoding:$this->htmlEncoding;
+ $doc = phpQuery::newDocumentHTML($html);
+ pq($doc)->find($tag_str)->remove();
+ $html = pq($doc)->htmlOuter();
+ $doc->unloadDocument();
+ }
+ return $html;
+ }
+
+ protected function destroyDocument()
+ {
+ if ($this->document instanceof phpQueryObject) {
+ $this->document->unloadDocument();
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->destroyDocument();
+ }
+}
diff --git a/vendor/jaeger/querylist/src/Exceptions/ServiceNotFoundException.php b/vendor/jaeger/querylist/src/Exceptions/ServiceNotFoundException.php
new file mode 100644
index 0000000..a8924ad
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Exceptions/ServiceNotFoundException.php
@@ -0,0 +1,15 @@
+
+ * Date: 2017/9/21
+ */
+
+namespace QL\Exceptions;
+
+use Exception;
+
+class ServiceNotFoundException extends Exception
+{
+
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Kernel.php b/vendor/jaeger/querylist/src/Kernel.php
new file mode 100644
index 0000000..085c21d
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Kernel.php
@@ -0,0 +1,74 @@
+
+ * Date: 2017/9/21
+ */
+
+namespace QL;
+
+use QL\Contracts\ServiceProviderContract;
+use QL\Exceptions\ServiceNotFoundException;
+use QL\Providers\EncodeServiceProvider;
+use Closure;
+use QL\Providers\HttpServiceProvider;
+use QL\Providers\PluginServiceProvider;
+use QL\Providers\SystemServiceProvider;
+use Tightenco\Collect\Support\Collection;
+
+class Kernel
+{
+ protected $providers = [
+ SystemServiceProvider::class,
+ HttpServiceProvider::class,
+ EncodeServiceProvider::class,
+ PluginServiceProvider::class
+ ];
+
+ protected $binds;
+ protected $ql;
+
+ /**
+ * Kernel constructor.
+ * @param $ql
+ */
+ public function __construct(QueryList $ql)
+ {
+ $this->ql = $ql;
+ $this->binds = new Collection();
+ }
+
+ public function bootstrap()
+ {
+ //注册服务提供者
+ $this->registerProviders();
+ return $this;
+ }
+
+ public function registerProviders()
+ {
+ foreach ($this->providers as $provider) {
+ $this->register(new $provider());
+ }
+ }
+
+ public function bind(string $name,Closure $provider)
+ {
+ $this->binds[$name] = $provider;
+ }
+
+ public function getService(string $name)
+ {
+ if(!$this->binds->offsetExists($name)){
+ throw new ServiceNotFoundException("Service: {$name} not found!");
+ }
+ return $this->binds[$name];
+ }
+
+ private function register(ServiceProviderContract $instance)
+ {
+ $instance->register($this);
+ }
+
+
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Providers/EncodeServiceProvider.php b/vendor/jaeger/querylist/src/Providers/EncodeServiceProvider.php
new file mode 100644
index 0000000..e8fd739
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Providers/EncodeServiceProvider.php
@@ -0,0 +1,22 @@
+
+ * Date: 2017/9/20
+ */
+
+namespace QL\Providers;
+
+use QL\Contracts\ServiceProviderContract;
+use QL\Kernel;
+use QL\Services\EncodeService;
+
+class EncodeServiceProvider implements ServiceProviderContract
+{
+ public function register(Kernel $kernel)
+ {
+ $kernel->bind('encoding',function (string $outputEncoding,string $inputEncoding = null){
+ return EncodeService::convert($this,$outputEncoding,$inputEncoding);
+ });
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Providers/HttpServiceProvider.php b/vendor/jaeger/querylist/src/Providers/HttpServiceProvider.php
new file mode 100644
index 0000000..2b3145f
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Providers/HttpServiceProvider.php
@@ -0,0 +1,40 @@
+
+ * Date: 2017/9/22
+ */
+
+namespace QL\Providers;
+
+
+use QL\Contracts\ServiceProviderContract;
+use QL\Kernel;
+use QL\Services\HttpService;
+use QL\Services\MultiRequestService;
+
+class HttpServiceProvider implements ServiceProviderContract
+{
+ public function register(Kernel $kernel)
+ {
+ $kernel->bind('get',function (...$args){
+ return HttpService::get($this,...$args);
+ });
+
+ $kernel->bind('post',function (...$args){
+ return HttpService::post($this,...$args);
+ });
+
+ $kernel->bind('postJson',function (...$args){
+ return HttpService::postJson($this,...$args);
+ });
+
+ $kernel->bind('multiGet',function (...$args){
+ return new MultiRequestService($this,'get',...$args);
+ });
+
+ $kernel->bind('multiPost',function (...$args){
+ return new MultiRequestService($this,'post',...$args);
+ });
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Providers/PluginServiceProvider.php b/vendor/jaeger/querylist/src/Providers/PluginServiceProvider.php
new file mode 100644
index 0000000..867b2ec
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Providers/PluginServiceProvider.php
@@ -0,0 +1,23 @@
+
+ * Date: 2017/9/22
+ */
+
+namespace QL\Providers;
+
+use QL\Contracts\ServiceProviderContract;
+use QL\Kernel;
+use QL\Services\PluginService;
+
+class PluginServiceProvider implements ServiceProviderContract
+{
+ public function register(Kernel $kernel)
+ {
+ $kernel->bind('use',function ($plugins,...$opt){
+ return PluginService::install($this,$plugins,...$opt);
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Providers/SystemServiceProvider.php b/vendor/jaeger/querylist/src/Providers/SystemServiceProvider.php
new file mode 100644
index 0000000..76d60d2
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Providers/SystemServiceProvider.php
@@ -0,0 +1,32 @@
+
+ * Date: 2017/9/22
+ */
+
+namespace QL\Providers;
+
+use QL\Contracts\ServiceProviderContract;
+use QL\Kernel;
+use Closure;
+
+class SystemServiceProvider implements ServiceProviderContract
+{
+ public function register(Kernel $kernel)
+ {
+ $kernel->bind('html',function (...$args){
+ $this->setHtml(...$args);
+ return $this;
+ });
+
+ $kernel->bind('queryData',function (Closure $callback = null){
+ return $this->query()->getData($callback)->all();
+ });
+
+ $kernel->bind('pipe',function (Closure $callback = null){
+ return $callback($this);
+ });
+
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/QueryList.php b/vendor/jaeger/querylist/src/QueryList.php
new file mode 100644
index 0000000..4fc1c6c
--- /dev/null
+++ b/vendor/jaeger/querylist/src/QueryList.php
@@ -0,0 +1,133 @@
+query = new Query($this);
+ $this->kernel = (new Kernel($this))->bootstrap();
+ Config::getInstance()->bootstrap($this);
+ }
+
+ public function __call($name, $arguments)
+ {
+ if(method_exists($this->query,$name)){
+ $result = $this->query->$name(...$arguments);
+ }else{
+ $result = $this->kernel->getService($name)->call($this,...$arguments);
+ }
+ return $result;
+ }
+
+ public static function __callStatic($name, $arguments)
+ {
+ $instance = new self();
+ return $instance->$name(...$arguments);
+ }
+
+ public function __destruct()
+ {
+ $this->destruct();
+ }
+
+ /**
+ * Get the QueryList single instance
+ *
+ * @return QueryList
+ */
+ public static function getInstance()
+ {
+ self::$instance || self::$instance = new self();
+ return self::$instance;
+ }
+
+ /**
+ * Get the Config instance
+ * @return null|Config
+ */
+ public static function config()
+ {
+ return Config::getInstance();
+ }
+
+ /**
+ * Destruction of resources
+ */
+ public function destruct()
+ {
+ unset($this->query);
+ unset($this->kernel);
+ }
+
+ /**
+ * Destroy all documents
+ */
+ public static function destructDocuments()
+ {
+ phpQuery::$documents = [];
+ }
+
+ /**
+ * Bind a custom method to the QueryList object
+ *
+ * @param string $name Invoking the name
+ * @param Closure $provide Called method
+ * @return $this
+ */
+ public function bind(string $name,Closure $provide)
+ {
+ $this->kernel->bind($name,$provide);
+ return $this;
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Services/EncodeService.php b/vendor/jaeger/querylist/src/Services/EncodeService.php
new file mode 100644
index 0000000..704d8b8
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Services/EncodeService.php
@@ -0,0 +1,37 @@
+
+ * Date: 2017/9/20
+ * 编码转换服务
+ */
+
+namespace QL\Services;
+
+use QL\QueryList;
+
+class EncodeService
+{
+ public static function convert(QueryList $ql,string $outputEncoding,string $inputEncoding = null)
+ {
+ $html = $ql->getHtml();
+ $inputEncoding || $inputEncoding = self::detect($html);
+ $html = iconv($inputEncoding,$outputEncoding.'//IGNORE',$html);
+ $ql->setHtml($html);
+ return $ql;
+ }
+
+ /**
+ * Attempts to detect the encoding
+ * @param $string
+ * @return bool|false|mixed|string
+ */
+ public static function detect($string)
+ {
+ $charset=mb_detect_encoding($string, array('ASCII', 'GB2312', 'GBK', 'UTF-8'),true);
+ if(strtolower($charset)=='cp936')
+ $charset='GBK';
+ return $charset;
+ }
+
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Services/HttpService.php b/vendor/jaeger/querylist/src/Services/HttpService.php
new file mode 100644
index 0000000..0d2cdec
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Services/HttpService.php
@@ -0,0 +1,59 @@
+
+ * Date: 2017/9/22
+ */
+
+namespace QL\Services;
+
+use GuzzleHttp\Cookie\CookieJar;
+use Jaeger\GHttp;
+use QL\QueryList;
+
+class HttpService
+{
+ protected static $cookieJar = null;
+
+ public static function getCookieJar()
+ {
+ if(self::$cookieJar == null)
+ {
+ self::$cookieJar = new CookieJar();
+ }
+ return self::$cookieJar;
+ }
+
+ public static function get(QueryList $ql,$url,$args = null,$otherArgs = [])
+ {
+ $otherArgs = array_merge([
+ 'cookies' => self::getCookieJar(),
+ 'verify' => false
+ ],$otherArgs);
+ $html = GHttp::get($url,$args,$otherArgs);
+ $ql->setHtml($html);
+ return $ql;
+ }
+
+ public static function post(QueryList $ql,$url,$args = null,$otherArgs = [])
+ {
+ $otherArgs = array_merge([
+ 'cookies' => self::getCookieJar(),
+ 'verify' => false
+ ],$otherArgs);
+ $html = GHttp::post($url,$args,$otherArgs);
+ $ql->setHtml($html);
+ return $ql;
+ }
+
+ public static function postJson(QueryList $ql,$url,$args = null,$otherArgs = [])
+ {
+ $otherArgs = array_merge([
+ 'cookies' => self::getCookieJar(),
+ 'verify' => false
+ ],$otherArgs);
+ $html = GHttp::postJson($url,$args,$otherArgs);
+ $ql->setHtml($html);
+ return $ql;
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Services/MultiRequestService.php b/vendor/jaeger/querylist/src/Services/MultiRequestService.php
new file mode 100644
index 0000000..803ee80
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Services/MultiRequestService.php
@@ -0,0 +1,66 @@
+
+ * Date: 18/12/10
+ * Time: 下午7:05
+ */
+
+namespace QL\Services;
+
+
+use Jaeger\GHttp;
+use Closure;
+use GuzzleHttp\Psr7\Response;
+use QL\QueryList;
+use GuzzleHttp\Exception\RequestException;
+
+/**
+ * Class MultiRequestService
+ * @package QL\Services
+ *
+ * @method MultiRequestService withHeaders($headers)
+ * @method MultiRequestService withOptions($options)
+ * @method MultiRequestService concurrency($concurrency)
+ */
+class MultiRequestService
+{
+ protected $ql;
+ protected $multiRequest;
+ protected $method;
+
+ public function __construct(QueryList $ql,$method,$urls)
+ {
+ $this->ql = $ql;
+ $this->method = $method;
+ $this->multiRequest = GHttp::multiRequest($urls);
+ }
+
+ public function __call($name, $arguments)
+ {
+ $this->multiRequest = $this->multiRequest->$name(...$arguments);
+ return $this;
+ }
+
+ public function success(Closure $success)
+ {
+ $this->multiRequest = $this->multiRequest->success(function(Response $response, $index) use($success){
+ $this->ql->setHtml((String)$response->getBody());
+ $success($this->ql,$response, $index);
+ });
+ return $this;
+ }
+
+ public function error(Closure $error)
+ {
+ $this->multiRequest = $this->multiRequest->error(function(RequestException $reason, $index) use($error){
+ $error($this->ql,$reason, $index);
+ });
+ return $this;
+ }
+
+ public function send()
+ {
+ $this->multiRequest->{$this->method}();
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/src/Services/PluginService.php b/vendor/jaeger/querylist/src/Services/PluginService.php
new file mode 100644
index 0000000..1afef3a
--- /dev/null
+++ b/vendor/jaeger/querylist/src/Services/PluginService.php
@@ -0,0 +1,26 @@
+
+ * Date: 2017/9/22
+ */
+
+namespace QL\Services;
+
+use QL\QueryList;
+
+class PluginService
+{
+ public static function install(QueryList $queryList, $plugins, ...$opt)
+ {
+ if(is_array($plugins))
+ {
+ foreach ($plugins as $plugin) {
+ $plugin::install($queryList);
+ }
+ }else{
+ $plugins::install($queryList,...$opt);
+ }
+ return $queryList;
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/tests/Dom/FindTest.php b/vendor/jaeger/querylist/tests/Dom/FindTest.php
new file mode 100644
index 0000000..024df21
--- /dev/null
+++ b/vendor/jaeger/querylist/tests/Dom/FindTest.php
@@ -0,0 +1,71 @@
+html = $this->getSnippet('snippet-1');
+ $this->ql = QueryList::html($this->html);
+ }
+
+ /**
+ * @test
+ */
+ public function find_first_dom_attr()
+ {
+ $img = [];
+ $img[] = $this->ql->find('img')->attr('src');
+ $img[] = $this->ql->find('img')->src;
+ $img[] = $this->ql->find('img:eq(0)')->src;
+ $img[] = $this->ql->find('img')->eq(0)->src;
+
+ $alt = $this->ql->find('img')->alt;
+ $abc = $this->ql->find('img')->abc;
+
+ $this->assertCount(1,array_unique($img));
+ $this->assertEquals($alt,'这是图片');
+ $this->assertEquals($abc,'这是一个自定义属性');
+
+ }
+
+ /**
+ * @test
+ */
+ public function find_second_dom_attr()
+ {
+
+ $img2 = [];
+ $img2[] = $this->ql->find('img')->eq(1)->alt;
+ $img2[] = $this->ql->find('img:eq(1)')->alt;
+ $img2[] = $this->ql->find('.second_pic')->alt;
+
+ $this->assertCount(1,array_unique($img2));
+
+ }
+
+ /**
+ * @test
+ */
+ public function find_dom_all_attr()
+ {
+ $imgAttr = $this->ql->find('img:eq(0)')->attr('*');
+ $linkAttr = $this->ql->find('a:eq(1)')->attr('*');
+ $this->assertCount(3,$imgAttr);
+ $this->assertCount(1,$linkAttr);
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/tests/Dom/RulesTest.php b/vendor/jaeger/querylist/tests/Dom/RulesTest.php
new file mode 100644
index 0000000..7c555ca
--- /dev/null
+++ b/vendor/jaeger/querylist/tests/Dom/RulesTest.php
@@ -0,0 +1,43 @@
+
+ * Date: 18/12/12
+ * Time: 下午12:25
+ */
+
+namespace Tests\Dom;
+
+
+use QL\QueryList;
+use Tests\TestCaseBase;
+use Tightenco\Collect\Support\Collection;
+
+class RulesTest extends TestCaseBase
+{
+ protected $html;
+ protected $ql;
+
+ protected function setUp(): void
+ {
+ $this->html = $this->getSnippet('snippet-2');
+ $this->ql = QueryList::html($this->html);
+ }
+
+ /**
+ * @test
+ */
+ public function get_data_by_rules()
+ {
+ $rules = [
+ 'a' => ['a','text'],
+ 'img_src' => ['img','src'],
+ 'img_alt' => ['img','alt']
+ ];
+ $range = 'ul>li';
+ $data = QueryList::rules($rules)->range($range)->html($this->html)->query()->getData();
+ $this->assertInstanceOf(Collection::class,$data);
+ $this->assertCount(3,$data);
+ $this->assertEquals('http://querylist.com/2.jpg',$data[1]['img_src']);
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/tests/Feature/HttpTest.php b/vendor/jaeger/querylist/tests/Feature/HttpTest.php
new file mode 100644
index 0000000..0d723ed
--- /dev/null
+++ b/vendor/jaeger/querylist/tests/Feature/HttpTest.php
@@ -0,0 +1,103 @@
+urls = [
+ 'http://httpbin.org/get?name=php',
+ 'http://httpbin.org/get?name=golang',
+ 'http://httpbin.org/get?name=c++',
+ 'http://httpbin.org/get?name=java'
+ ];
+ }
+
+ /**
+ * @test
+ */
+ public function can_post_json_data()
+ {
+ $mock = new MockHandler([new Response()]);
+ $data = [
+ 'name' => 'foo'
+ ];
+ QueryList::postJson('http://foo.com',$data,[
+ 'handler' => $mock
+ ]);
+ $this->assertEquals((string)$mock->getLastRequest()->getBody(),json_encode($data));
+ }
+
+ /**
+ * @test
+ */
+ public function concurrent_requests_base_use()
+ {
+ $urls = $this->urls;
+ QueryList::getInstance()
+ ->multiGet($urls)
+ ->success(function(QueryList $ql,Response $response, $index) use($urls){
+ $body = json_decode((string)$response->getBody(),true);
+ $this->assertEquals($urls[$index],$body['url']);
+ })->send();
+ }
+
+ /**
+ * @test
+ */
+ public function concurrent_requests_advanced_use()
+ {
+ $ua = 'QueryList/4.0';
+
+ $errorUrl = 'http://web-site-not-exist.com';
+ $urls = array_merge($this->urls,[$errorUrl]);
+
+ QueryList::rules([])
+ ->multiGet($urls)
+ ->concurrency(2)
+ ->withOptions([
+ 'timeout' => 60
+ ])
+ ->withHeaders([
+ 'User-Agent' => $ua
+ ])
+ ->success(function (QueryList $ql, Response $response, $index) use($ua){
+ $body = json_decode((string)$response->getBody(),true);
+ $this->assertEquals($ua,$body['headers']['User-Agent']);
+ })
+ ->error(function (QueryList $ql, $reason, $index) use($urls,$errorUrl){
+ $this->assertEquals($urls[$index],$errorUrl);
+ })
+ ->send();
+ }
+
+ /**
+ * @test
+ */
+ public function request_with_cache()
+ {
+ $url = $this->urls[0];
+ $data = QueryList::get($url,null,[
+ 'cache' => sys_get_temp_dir(),
+ 'cache_ttl' => 600
+ ])->getHtml();
+ $data = json_decode($data,true);
+ $this->assertEquals($url,$data['url']);
+
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/tests/Feature/InstanceTest.php b/vendor/jaeger/querylist/tests/Feature/InstanceTest.php
new file mode 100644
index 0000000..a659060
--- /dev/null
+++ b/vendor/jaeger/querylist/tests/Feature/InstanceTest.php
@@ -0,0 +1,48 @@
+html = $this->getSnippet('snippet-1');
+ }
+ /**
+ * @test
+ */
+ public function singleton_instance_mode()
+ {
+ $ql = QueryList::getInstance()->html($this->html);
+ $ql2 = QueryList::getInstance();
+ $this->assertEquals($ql->getHtml(),$ql2->getHtml());
+
+
+ }
+
+ /**
+ * @test
+ */
+ public function get_new_object()
+ {
+ $ql = (new QueryList())->html($this->html);
+ $ql2 = (new QueryList())->html('');
+ $this->assertNotEquals($ql->getHtml(),$ql2->getHtml());
+
+ $ql = QueryList::range('')->html($this->html);
+ $ql2 = QueryList::range('')->html('');
+ $this->assertNotEquals($ql->getHtml(),$ql2->getHtml());
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/tests/Feature/MethodTest.php b/vendor/jaeger/querylist/tests/Feature/MethodTest.php
new file mode 100644
index 0000000..ca10cf9
--- /dev/null
+++ b/vendor/jaeger/querylist/tests/Feature/MethodTest.php
@@ -0,0 +1,36 @@
+html = $this->getSnippet('snippet-1');
+ }
+
+ /**
+ * @test
+ */
+ public function pipe()
+ {
+ $html = $this->html;
+ $qlHtml = QueryList::pipe(function(QueryList $ql) use($html){
+ $ql->setHtml($html);
+ return $ql;
+ })->getHtml(false);
+ $this->assertEquals($html,$qlHtml);
+ }
+}
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/tests/TestCaseBase.php b/vendor/jaeger/querylist/tests/TestCaseBase.php
new file mode 100644
index 0000000..c88d73e
--- /dev/null
+++ b/vendor/jaeger/querylist/tests/TestCaseBase.php
@@ -0,0 +1,20 @@
+
+
+ 其它的一些 文本
+
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/tests/assets/snippet-2.html b/vendor/jaeger/querylist/tests/assets/snippet-2.html
new file mode 100644
index 0000000..88ae3f0
--- /dev/null
+++ b/vendor/jaeger/querylist/tests/assets/snippet-2.html
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/vendor/jaeger/querylist/tests/bootstrap.php b/vendor/jaeger/querylist/tests/bootstrap.php
new file mode 100644
index 0000000..eace7af
--- /dev/null
+++ b/vendor/jaeger/querylist/tests/bootstrap.php
@@ -0,0 +1,5 @@
+ 'taoser\\addons\\Service',
diff --git a/vendor/taoser/think-addons/src/addons/Route.php b/vendor/taoser/think-addons/src/addons/Route.php
index 86ed94d..1fe6e2a 100644
--- a/vendor/taoser/think-addons/src/addons/Route.php
+++ b/vendor/taoser/think-addons/src/addons/Route.php
@@ -13,6 +13,7 @@ declare(strict_types=1);
namespace taoser\addons;
+use think\facade\Lang;
use think\helper\Str;
use think\facade\Event;
use think\facade\Config;
diff --git a/vendor/tightenco/collect/.github/workflows/run-tests.yml b/vendor/tightenco/collect/.github/workflows/run-tests.yml
new file mode 100644
index 0000000..2a0d42f
--- /dev/null
+++ b/vendor/tightenco/collect/.github/workflows/run-tests.yml
@@ -0,0 +1,41 @@
+name: Run tests
+
+on:
+ push:
+ branches: [laravel-9-ongoing, laravel-8-ongoing]
+ pull_request:
+
+jobs:
+ tests:
+ strategy:
+ matrix:
+ os: [Ubuntu, macOS]
+ php: [7.3, 7.4, 8.0, 8.1]
+
+ include:
+ - os: Ubuntu
+ os-version: ubuntu-latest
+
+ - os: macOS
+ os-version: macos-latest
+
+ name: ${{ matrix.os }} - PHP ${{ matrix.php }}
+
+ runs-on: ${{ matrix.os-version }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v1
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: posix, dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
+ coverage: none
+
+ - name: Install dependencies
+ run: composer update --prefer-stable --prefer-dist --no-interaction
+
+ - name: Run tests
+ run: bash upgrade.sh
diff --git a/vendor/tightenco/collect/branch-commit-push.sh b/vendor/tightenco/collect/branch-commit-push.sh
new file mode 100644
index 0000000..124e583
--- /dev/null
+++ b/vendor/tightenco/collect/branch-commit-push.sh
@@ -0,0 +1,67 @@
+#!/bin/bash
+
+
+GREEN='\033[0;32m'
+RED='\033[0;31m'
+WHITE='\033[0;37m'
+RESET='\033[0m'
+
+function validateVersion()
+{
+ echo ""
+ passedVersion=$1
+ echo -e "${WHITE}-- Validating tag '$passedVersion'...${RESET}"
+
+ # Todo: validate the version here using a regex; if fail, just exit
+ # ... expect 8.75.0, with no v in front of it
+
+ if [[ $passedVersion == '' ]]; then
+ echo -e "\n-- Invalid tag. Tags should be structured without v; e.g. 8.57.0"
+ exit
+ fi
+
+ echo -e "${WHITE}-- Tag valid.${RESET}"
+ echo ""
+}
+
+# Exit script if any command fails (e.g. phpunit)
+set -e
+
+
+# Require confirmation it's set up corrctly
+echo
+echo -e "${WHITE}-- This script is meant to be run after running upgrade.sh, BEFORE committing to Git.${RESET}"
+
+while true; do
+ echo -e "${GREEN}-- Is that the current state of your local project?${RESET}"
+ read -p "-- (y/n) " yn
+ case $yn in
+ [Yy]* ) break;;
+ [Nn]* ) exit;;
+ * ) echo "Please answer y or n.";;
+ esac
+done
+
+# Get the version and exit if not valid
+validateVersion $1
+
+# Create official v prefaced version
+version="v$1"
+
+# Run tests (and bail if they fail)
+phpunit
+echo -e "\n${WHITE}-- Tests succeeded.${RESET}"
+
+# Branch
+echo -e "\n${WHITE}-- Creating a Git branch '$version-changes'...${RESET}\n"
+git checkout -b $version-changes
+
+# Add and commit, with "v8.57.0 changes" as the commit name
+git add -A
+git commit -m "$version changes"
+
+echo
+echo -e "${WHITE}-- Git committed.${RESET}"
+
+# Push
+git push -u origin $version-changes
diff --git a/vendor/tightenco/collect/composer.json b/vendor/tightenco/collect/composer.json
new file mode 100644
index 0000000..88ebb77
--- /dev/null
+++ b/vendor/tightenco/collect/composer.json
@@ -0,0 +1,51 @@
+{
+ "name": "tightenco/collect",
+ "description": "Collect - Illuminate Collections as a separate package.",
+ "keywords": ["laravel", "collection"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Taylor Otwell",
+ "email": "taylorotwell@gmail.com"
+ }
+ ],
+ "require": {
+ "php": "^7.3|^8.0",
+ "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.0",
+ "phpunit/phpunit": "^8.3",
+ "nesbot/carbon": "^2.23.0"
+ },
+ "autoload": {
+ "files": [
+ "src/Collect/Support/helpers.php",
+ "src/Collect/Support/alias.php"
+ ],
+ "psr-4": {
+ "Tightenco\\Collect\\": "src/Collect"
+ }
+ },
+ "autoload-dev": {
+ "files": [
+ "tests/files/Support/Carbon.php",
+ "tests/files/Support/HtmlString.php",
+ "tests/files/Support/HigherOrderTapProxy.php",
+ "tests/files/Support/Str.php",
+ "tests/files/Support/Traits/Conditionable.php",
+ "tests/files/Support/Stringable.php",
+ "tests/files/Support/ItemNotFoundException.php",
+ "tests/files/Support/MultipleItemsFoundException.php",
+ "tests/Support/Concerns/CountsEnumerations.php"
+ ]
+ },
+ "scripts": {
+ "test": [
+ "@composer install",
+ "phpunit"
+ ]
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/vendor/tightenco/collect/framework-.zip b/vendor/tightenco/collect/framework-.zip
new file mode 100644
index 0000000..e69de29
diff --git a/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php b/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php
new file mode 100644
index 0000000..a205804
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Contracts/Support/Arrayable.php
@@ -0,0 +1,13 @@
+all();
+ } elseif (! is_array($values)) {
+ continue;
+ }
+
+ $results[] = $values;
+ }
+
+ return array_merge([], ...$results);
+ }
+
+ /**
+ * Cross join the given arrays, returning all possible permutations.
+ *
+ * @param iterable ...$arrays
+ * @return array
+ */
+ public static function crossJoin(...$arrays)
+ {
+ $results = [[]];
+
+ foreach ($arrays as $index => $array) {
+ $append = [];
+
+ foreach ($results as $product) {
+ foreach ($array as $item) {
+ $product[$index] = $item;
+
+ $append[] = $product;
+ }
+ }
+
+ $results = $append;
+ }
+
+ return $results;
+ }
+
+ /**
+ * Divide an array into two arrays. One with keys and the other with values.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function divide($array)
+ {
+ return [array_keys($array), array_values($array)];
+ }
+
+ /**
+ * Flatten a multi-dimensional associative array with dots.
+ *
+ * @param iterable $array
+ * @param string $prepend
+ * @return array
+ */
+ public static function dot($array, $prepend = '')
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ if (is_array($value) && ! empty($value)) {
+ $results = array_merge($results, static::dot($value, $prepend.$key.'.'));
+ } else {
+ $results[$prepend.$key] = $value;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Convert a flatten "dot" notation array into an expanded array.
+ *
+ * @param iterable $array
+ * @return array
+ */
+ public static function undot($array)
+ {
+ $results = [];
+
+ foreach ($array as $key => $value) {
+ static::set($results, $key, $value);
+ }
+
+ return $results;
+ }
+
+ /**
+ * Get all of the given array except for a specified array of keys.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return array
+ */
+ public static function except($array, $keys)
+ {
+ static::forget($array, $keys);
+
+ return $array;
+ }
+
+ /**
+ * Determine if the given key exists in the provided array.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|int $key
+ * @return bool
+ */
+ public static function exists($array, $key)
+ {
+ if ($array instanceof Enumerable) {
+ return $array->has($key);
+ }
+
+ if ($array instanceof ArrayAccess) {
+ return $array->offsetExists($key);
+ }
+
+ return array_key_exists($key, $array);
+ }
+
+ /**
+ * Return the first element in an array passing a given truth test.
+ *
+ * @param iterable $array
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function first($array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ if (empty($array)) {
+ return value($default);
+ }
+
+ foreach ($array as $item) {
+ return $item;
+ }
+ }
+
+ foreach ($array as $key => $value) {
+ if ($callback($value, $key)) {
+ return $value;
+ }
+ }
+
+ return value($default);
+ }
+
+ /**
+ * Return the last element in an array passing a given truth test.
+ *
+ * @param array $array
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function last($array, callable $callback = null, $default = null)
+ {
+ if (is_null($callback)) {
+ return empty($array) ? value($default) : end($array);
+ }
+
+ return static::first(array_reverse($array, true), $callback, $default);
+ }
+
+ /**
+ * Flatten a multi-dimensional array into a single level.
+ *
+ * @param iterable $array
+ * @param int $depth
+ * @return array
+ */
+ public static function flatten($array, $depth = INF)
+ {
+ $result = [];
+
+ foreach ($array as $item) {
+ $item = $item instanceof Collection ? $item->all() : $item;
+
+ if (! is_array($item)) {
+ $result[] = $item;
+ } else {
+ $values = $depth === 1
+ ? array_values($item)
+ : static::flatten($item, $depth - 1);
+
+ foreach ($values as $value) {
+ $result[] = $value;
+ }
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove one or many array items from a given array using "dot" notation.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return void
+ */
+ public static function forget(&$array, $keys)
+ {
+ $original = &$array;
+
+ $keys = (array) $keys;
+
+ if (count($keys) === 0) {
+ return;
+ }
+
+ foreach ($keys as $key) {
+ // if the exact key exists in the top-level, remove it
+ if (static::exists($array, $key)) {
+ unset($array[$key]);
+
+ continue;
+ }
+
+ $parts = explode('.', $key);
+
+ // clean up before each pass
+ $array = &$original;
+
+ while (count($parts) > 1) {
+ $part = array_shift($parts);
+
+ if (isset($array[$part]) && is_array($array[$part])) {
+ $array = &$array[$part];
+ } else {
+ continue 2;
+ }
+ }
+
+ unset($array[array_shift($parts)]);
+ }
+ }
+
+ /**
+ * Get an item from an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|int|null $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function get($array, $key, $default = null)
+ {
+ if (! static::accessible($array)) {
+ return value($default);
+ }
+
+ if (is_null($key)) {
+ return $array;
+ }
+
+ if (static::exists($array, $key)) {
+ return $array[$key];
+ }
+
+ if (strpos($key, '.') === false) {
+ return $array[$key] ?? value($default);
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($array) && static::exists($array, $segment)) {
+ $array = $array[$segment];
+ } else {
+ return value($default);
+ }
+ }
+
+ return $array;
+ }
+
+ /**
+ * Check if an item or items exist in an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|array $keys
+ * @return bool
+ */
+ public static function has($array, $keys)
+ {
+ $keys = (array) $keys;
+
+ if (! $array || $keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ $subKeyArray = $array;
+
+ if (static::exists($array, $key)) {
+ continue;
+ }
+
+ foreach (explode('.', $key) as $segment) {
+ if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) {
+ $subKeyArray = $subKeyArray[$segment];
+ } else {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine if any of the keys exist in an array using "dot" notation.
+ *
+ * @param \ArrayAccess|array $array
+ * @param string|array $keys
+ * @return bool
+ */
+ public static function hasAny($array, $keys)
+ {
+ if (is_null($keys)) {
+ return false;
+ }
+
+ $keys = (array) $keys;
+
+ if (! $array) {
+ return false;
+ }
+
+ if ($keys === []) {
+ return false;
+ }
+
+ foreach ($keys as $key) {
+ if (static::has($array, $key)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines if an array is associative.
+ *
+ * An array is "associative" if it doesn't have sequential numerical keys beginning with zero.
+ *
+ * @param array $array
+ * @return bool
+ */
+ public static function isAssoc(array $array)
+ {
+ $keys = array_keys($array);
+
+ return array_keys($keys) !== $keys;
+ }
+
+ /**
+ * Determines if an array is a list.
+ *
+ * An array is a "list" if all array keys are sequential integers starting from 0 with no gaps in between.
+ *
+ * @param array $array
+ * @return bool
+ */
+ public static function isList($array)
+ {
+ return ! self::isAssoc($array);
+ }
+
+ /**
+ * Get a subset of the items from the given array.
+ *
+ * @param array $array
+ * @param array|string $keys
+ * @return array
+ */
+ public static function only($array, $keys)
+ {
+ return array_intersect_key($array, array_flip((array) $keys));
+ }
+
+ /**
+ * Pluck an array of values from an array.
+ *
+ * @param iterable $array
+ * @param string|array|int|null $value
+ * @param string|array|null $key
+ * @return array
+ */
+ public static function pluck($array, $value, $key = null)
+ {
+ $results = [];
+
+ [$value, $key] = static::explodePluckParameters($value, $key);
+
+ foreach ($array as $item) {
+ $itemValue = data_get($item, $value);
+
+ // If the key is "null", we will just append the value to the array and keep
+ // looping. Otherwise we will key the array using the value of the key we
+ // received from the developer. Then we'll return the final array form.
+ if (is_null($key)) {
+ $results[] = $itemValue;
+ } else {
+ $itemKey = data_get($item, $key);
+
+ if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
+ $itemKey = (string) $itemKey;
+ }
+
+ $results[$itemKey] = $itemValue;
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Explode the "value" and "key" arguments passed to "pluck".
+ *
+ * @param string|array $value
+ * @param string|array|null $key
+ * @return array
+ */
+ protected static function explodePluckParameters($value, $key)
+ {
+ $value = is_string($value) ? explode('.', $value) : $value;
+
+ $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
+
+ return [$value, $key];
+ }
+
+ /**
+ * Push an item onto the beginning of an array.
+ *
+ * @param array $array
+ * @param mixed $value
+ * @param mixed $key
+ * @return array
+ */
+ public static function prepend($array, $value, $key = null)
+ {
+ if (func_num_args() == 2) {
+ array_unshift($array, $value);
+ } else {
+ $array = [$key => $value] + $array;
+ }
+
+ return $array;
+ }
+
+ /**
+ * Get a value from the array, and remove it.
+ *
+ * @param array $array
+ * @param string|int $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public static function pull(&$array, $key, $default = null)
+ {
+ $value = static::get($array, $key, $default);
+
+ static::forget($array, $key);
+
+ return $value;
+ }
+
+ /**
+ * Convert the array into a query string.
+ *
+ * @param array $array
+ * @return string
+ */
+ public static function query($array)
+ {
+ return http_build_query($array, '', '&', PHP_QUERY_RFC3986);
+ }
+
+ /**
+ * Get one or a specified number of random values from an array.
+ *
+ * @param array $array
+ * @param int|null $number
+ * @param bool|false $preserveKeys
+ * @return mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public static function random($array, $number = null, $preserveKeys = false)
+ {
+ $requested = is_null($number) ? 1 : $number;
+
+ $count = count($array);
+
+ if ($requested > $count) {
+ throw new InvalidArgumentException(
+ "You requested {$requested} items, but there are only {$count} items available."
+ );
+ }
+
+ if (is_null($number)) {
+ return $array[array_rand($array)];
+ }
+
+ if ((int) $number === 0) {
+ return [];
+ }
+
+ $keys = array_rand($array, $number);
+
+ $results = [];
+
+ if ($preserveKeys) {
+ foreach ((array) $keys as $key) {
+ $results[$key] = $array[$key];
+ }
+ } else {
+ foreach ((array) $keys as $key) {
+ $results[] = $array[$key];
+ }
+ }
+
+ return $results;
+ }
+
+ /**
+ * Set an array item to a given value using "dot" notation.
+ *
+ * If no key is given to the method, the entire array will be replaced.
+ *
+ * @param array $array
+ * @param string|null $key
+ * @param mixed $value
+ * @return array
+ */
+ public static function set(&$array, $key, $value)
+ {
+ if (is_null($key)) {
+ return $array = $value;
+ }
+
+ $keys = explode('.', $key);
+
+ foreach ($keys as $i => $key) {
+ if (count($keys) === 1) {
+ break;
+ }
+
+ unset($keys[$i]);
+
+ // If the key doesn't exist at this depth, we will just create an empty array
+ // to hold the next value, allowing us to create the arrays to hold final
+ // values at the correct depth. Then we'll keep digging into the array.
+ if (! isset($array[$key]) || ! is_array($array[$key])) {
+ $array[$key] = [];
+ }
+
+ $array = &$array[$key];
+ }
+
+ $array[array_shift($keys)] = $value;
+
+ return $array;
+ }
+
+ /**
+ * Shuffle the given array and return the result.
+ *
+ * @param array $array
+ * @param int|null $seed
+ * @return array
+ */
+ public static function shuffle($array, $seed = null)
+ {
+ if (is_null($seed)) {
+ shuffle($array);
+ } else {
+ mt_srand($seed);
+ shuffle($array);
+ mt_srand();
+ }
+
+ return $array;
+ }
+
+ /**
+ * Sort the array using the given callback or "dot" notation.
+ *
+ * @param array $array
+ * @param callable|array|string|null $callback
+ * @return array
+ */
+ public static function sort($array, $callback = null)
+ {
+ return Collection::make($array)->sortBy($callback)->all();
+ }
+
+ /**
+ * Recursively sort an array by keys and values.
+ *
+ * @param array $array
+ * @param int $options
+ * @param bool $descending
+ * @return array
+ */
+ public static function sortRecursive($array, $options = SORT_REGULAR, $descending = false)
+ {
+ foreach ($array as &$value) {
+ if (is_array($value)) {
+ $value = static::sortRecursive($value, $options, $descending);
+ }
+ }
+
+ if (static::isAssoc($array)) {
+ $descending
+ ? krsort($array, $options)
+ : ksort($array, $options);
+ } else {
+ $descending
+ ? rsort($array, $options)
+ : sort($array, $options);
+ }
+
+ return $array;
+ }
+
+ /**
+ * Conditionally compile classes from an array into a CSS class list.
+ *
+ * @param array $array
+ * @return string
+ */
+ public static function toCssClasses($array)
+ {
+ $classList = static::wrap($array);
+
+ $classes = [];
+
+ foreach ($classList as $class => $constraint) {
+ if (is_numeric($class)) {
+ $classes[] = $constraint;
+ } elseif ($constraint) {
+ $classes[] = $class;
+ }
+ }
+
+ return implode(' ', $classes);
+ }
+
+ /**
+ * Filter the array using the given callback.
+ *
+ * @param array $array
+ * @param callable $callback
+ * @return array
+ */
+ public static function where($array, callable $callback)
+ {
+ return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH);
+ }
+
+ /**
+ * Filter items where the value is not null.
+ *
+ * @param array $array
+ * @return array
+ */
+ public static function whereNotNull($array)
+ {
+ return static::where($array, function ($value) {
+ return ! is_null($value);
+ });
+ }
+
+ /**
+ * If the given value is not an array and not null, wrap it in one.
+ *
+ * @param mixed $value
+ * @return array
+ */
+ public static function wrap($value)
+ {
+ if (is_null($value)) {
+ return [];
+ }
+
+ return is_array($value) ? $value : [$value];
+ }
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/Collection.php b/vendor/tightenco/collect/src/Collect/Support/Collection.php
new file mode 100644
index 0000000..0dc7323
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/Collection.php
@@ -0,0 +1,1672 @@
+items = $this->getArrayableItems($items);
+ }
+
+ /**
+ * Create a collection with the given range.
+ *
+ * @param int $from
+ * @param int $to
+ * @return static
+ */
+ public static function range($from, $to)
+ {
+ return new static(range($from, $to));
+ }
+
+ /**
+ * Get all of the items in the collection.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ return $this->items;
+ }
+
+ /**
+ * Get a lazy collection for the items in this collection.
+ *
+ * @return \Tightenco\Collect\Support\LazyCollection
+ */
+ public function lazy()
+ {
+ return new LazyCollection($this->items);
+ }
+
+ /**
+ * Get the average value of a given key.
+ *
+ * @param callable|string|null $callback
+ * @return mixed
+ */
+ public function avg($callback = null)
+ {
+ $callback = $this->valueRetriever($callback);
+
+ $items = $this->map(function ($value) use ($callback) {
+ return $callback($value);
+ })->filter(function ($value) {
+ return ! is_null($value);
+ });
+
+ if ($count = $items->count()) {
+ return $items->sum() / $count;
+ }
+ }
+
+ /**
+ * Get the median of a given key.
+ *
+ * @param string|array|null $key
+ * @return mixed
+ */
+ public function median($key = null)
+ {
+ $values = (isset($key) ? $this->pluck($key) : $this)
+ ->filter(function ($item) {
+ return ! is_null($item);
+ })->sort()->values();
+
+ $count = $values->count();
+
+ if ($count === 0) {
+ return;
+ }
+
+ $middle = (int) ($count / 2);
+
+ if ($count % 2) {
+ return $values->get($middle);
+ }
+
+ return (new static([
+ $values->get($middle - 1), $values->get($middle),
+ ]))->average();
+ }
+
+ /**
+ * Get the mode of a given key.
+ *
+ * @param string|array|null $key
+ * @return array|null
+ */
+ public function mode($key = null)
+ {
+ if ($this->count() === 0) {
+ return;
+ }
+
+ $collection = isset($key) ? $this->pluck($key) : $this;
+
+ $counts = new static;
+
+ $collection->each(function ($value) use ($counts) {
+ $counts[$value] = isset($counts[$value]) ? $counts[$value] + 1 : 1;
+ });
+
+ $sorted = $counts->sort();
+
+ $highestValue = $sorted->last();
+
+ return $sorted->filter(function ($value) use ($highestValue) {
+ return $value == $highestValue;
+ })->sort()->keys()->all();
+ }
+
+ /**
+ * Collapse the collection of items into a single array.
+ *
+ * @return static
+ */
+ public function collapse()
+ {
+ return new static(Arr::collapse($this->items));
+ }
+
+ /**
+ * Determine if an item exists in the collection.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return bool
+ */
+ public function contains($key, $operator = null, $value = null)
+ {
+ if (func_num_args() === 1) {
+ if ($this->useAsCallable($key)) {
+ $placeholder = new stdClass;
+
+ return $this->first($key, $placeholder) !== $placeholder;
+ }
+
+ return in_array($key, $this->items);
+ }
+
+ return $this->contains($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Determine if an item is not contained in the collection.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return bool
+ */
+ public function doesntContain($key, $operator = null, $value = null)
+ {
+ return ! $this->contains(...func_get_args());
+ }
+
+ /**
+ * Cross join with the given lists, returning all possible permutations.
+ *
+ * @param mixed ...$lists
+ * @return static
+ */
+ public function crossJoin(...$lists)
+ {
+ return new static(Arr::crossJoin(
+ $this->items, ...array_map([$this, 'getArrayableItems'], $lists)
+ ));
+ }
+
+ /**
+ * Get the items in the collection that are not present in the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function diff($items)
+ {
+ return new static(array_diff($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Get the items in the collection that are not present in the given items, using the callback.
+ *
+ * @param mixed $items
+ * @param callable $callback
+ * @return static
+ */
+ public function diffUsing($items, callable $callback)
+ {
+ return new static(array_udiff($this->items, $this->getArrayableItems($items), $callback));
+ }
+
+ /**
+ * Get the items in the collection whose keys and values are not present in the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function diffAssoc($items)
+ {
+ return new static(array_diff_assoc($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Get the items in the collection whose keys and values are not present in the given items, using the callback.
+ *
+ * @param mixed $items
+ * @param callable $callback
+ * @return static
+ */
+ public function diffAssocUsing($items, callable $callback)
+ {
+ return new static(array_diff_uassoc($this->items, $this->getArrayableItems($items), $callback));
+ }
+
+ /**
+ * Get the items in the collection whose keys are not present in the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function diffKeys($items)
+ {
+ return new static(array_diff_key($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Get the items in the collection whose keys are not present in the given items, using the callback.
+ *
+ * @param mixed $items
+ * @param callable $callback
+ * @return static
+ */
+ public function diffKeysUsing($items, callable $callback)
+ {
+ return new static(array_diff_ukey($this->items, $this->getArrayableItems($items), $callback));
+ }
+
+ /**
+ * Retrieve duplicate items from the collection.
+ *
+ * @param callable|string|null $callback
+ * @param bool $strict
+ * @return static
+ */
+ public function duplicates($callback = null, $strict = false)
+ {
+ $items = $this->map($this->valueRetriever($callback));
+
+ $uniqueItems = $items->unique(null, $strict);
+
+ $compare = $this->duplicateComparator($strict);
+
+ $duplicates = new static;
+
+ foreach ($items as $key => $value) {
+ if ($uniqueItems->isNotEmpty() && $compare($value, $uniqueItems->first())) {
+ $uniqueItems->shift();
+ } else {
+ $duplicates[$key] = $value;
+ }
+ }
+
+ return $duplicates;
+ }
+
+ /**
+ * Retrieve duplicate items from the collection using strict comparison.
+ *
+ * @param callable|string|null $callback
+ * @return static
+ */
+ public function duplicatesStrict($callback = null)
+ {
+ return $this->duplicates($callback, true);
+ }
+
+ /**
+ * Get the comparison function to detect duplicates.
+ *
+ * @param bool $strict
+ * @return \Closure
+ */
+ protected function duplicateComparator($strict)
+ {
+ if ($strict) {
+ return function ($a, $b) {
+ return $a === $b;
+ };
+ }
+
+ return function ($a, $b) {
+ return $a == $b;
+ };
+ }
+
+ /**
+ * Get all items except for those with the specified keys.
+ *
+ * @param \Tightenco\Collect\Support\Collection|mixed $keys
+ * @return static
+ */
+ public function except($keys)
+ {
+ if ($keys instanceof Enumerable) {
+ $keys = $keys->all();
+ } elseif (! is_array($keys)) {
+ $keys = func_get_args();
+ }
+
+ return new static(Arr::except($this->items, $keys));
+ }
+
+ /**
+ * Run a filter over each of the items.
+ *
+ * @param callable|null $callback
+ * @return static
+ */
+ public function filter(callable $callback = null)
+ {
+ if ($callback) {
+ return new static(Arr::where($this->items, $callback));
+ }
+
+ return new static(array_filter($this->items));
+ }
+
+ /**
+ * Get the first item from the collection passing the given truth test.
+ *
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public function first(callable $callback = null, $default = null)
+ {
+ return Arr::first($this->items, $callback, $default);
+ }
+
+ /**
+ * Get a flattened array of the items in the collection.
+ *
+ * @param int $depth
+ * @return static
+ */
+ public function flatten($depth = INF)
+ {
+ return new static(Arr::flatten($this->items, $depth));
+ }
+
+ /**
+ * Flip the items in the collection.
+ *
+ * @return static
+ */
+ public function flip()
+ {
+ return new static(array_flip($this->items));
+ }
+
+ /**
+ * Remove an item from the collection by key.
+ *
+ * @param string|int|array $keys
+ * @return $this
+ */
+ public function forget($keys)
+ {
+ foreach ((array) $keys as $key) {
+ $this->offsetUnset($key);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get an item from the collection by key.
+ *
+ * @param mixed $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if (array_key_exists($key, $this->items)) {
+ return $this->items[$key];
+ }
+
+ return value($default);
+ }
+
+ /**
+ * Get an item from the collection by key or add it to collection if it does not exist.
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @return mixed
+ */
+ public function getOrPut($key, $value)
+ {
+ if (array_key_exists($key, $this->items)) {
+ return $this->items[$key];
+ }
+
+ $this->offsetSet($key, $value = value($value));
+
+ return $value;
+ }
+
+ /**
+ * Group an associative array by a field or using a callback.
+ *
+ * @param array|callable|string $groupBy
+ * @param bool $preserveKeys
+ * @return static
+ */
+ public function groupBy($groupBy, $preserveKeys = false)
+ {
+ if (! $this->useAsCallable($groupBy) && is_array($groupBy)) {
+ $nextGroups = $groupBy;
+
+ $groupBy = array_shift($nextGroups);
+ }
+
+ $groupBy = $this->valueRetriever($groupBy);
+
+ $results = [];
+
+ foreach ($this->items as $key => $value) {
+ $groupKeys = $groupBy($value, $key);
+
+ if (! is_array($groupKeys)) {
+ $groupKeys = [$groupKeys];
+ }
+
+ foreach ($groupKeys as $groupKey) {
+ $groupKey = is_bool($groupKey) ? (int) $groupKey : $groupKey;
+
+ if (! array_key_exists($groupKey, $results)) {
+ $results[$groupKey] = new static;
+ }
+
+ $results[$groupKey]->offsetSet($preserveKeys ? $key : null, $value);
+ }
+ }
+
+ $result = new static($results);
+
+ if (! empty($nextGroups)) {
+ return $result->map->groupBy($nextGroups, $preserveKeys);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Key an associative array by a field or using a callback.
+ *
+ * @param callable|string $keyBy
+ * @return static
+ */
+ public function keyBy($keyBy)
+ {
+ $keyBy = $this->valueRetriever($keyBy);
+
+ $results = [];
+
+ foreach ($this->items as $key => $item) {
+ $resolvedKey = $keyBy($item, $key);
+
+ if (is_object($resolvedKey)) {
+ $resolvedKey = (string) $resolvedKey;
+ }
+
+ $results[$resolvedKey] = $item;
+ }
+
+ return new static($results);
+ }
+
+ /**
+ * Determine if an item exists in the collection by key.
+ *
+ * @param mixed $key
+ * @return bool
+ */
+ public function has($key)
+ {
+ $keys = is_array($key) ? $key : func_get_args();
+
+ foreach ($keys as $value) {
+ if (! array_key_exists($value, $this->items)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determine if any of the keys exist in the collection.
+ *
+ * @param mixed $key
+ * @return bool
+ */
+ public function hasAny($key)
+ {
+ if ($this->isEmpty()) {
+ return false;
+ }
+
+ $keys = is_array($key) ? $key : func_get_args();
+
+ foreach ($keys as $value) {
+ if ($this->has($value)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Concatenate values of a given key as a string.
+ *
+ * @param string $value
+ * @param string|null $glue
+ * @return string
+ */
+ public function implode($value, $glue = null)
+ {
+ $first = $this->first();
+
+ if (is_array($first) || (is_object($first) && ! $first instanceof \Illuminate\Support\Stringable)) {
+ return implode($glue ?? '', $this->pluck($value)->all());
+ }
+
+ return implode($value ?? '', $this->items);
+ }
+
+ /**
+ * Intersect the collection with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function intersect($items)
+ {
+ return new static(array_intersect($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Intersect the collection with the given items by key.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function intersectByKeys($items)
+ {
+ return new static(array_intersect_key(
+ $this->items, $this->getArrayableItems($items)
+ ));
+ }
+
+ /**
+ * Determine if the collection is empty or not.
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return empty($this->items);
+ }
+
+ /**
+ * Determine if the collection contains a single item.
+ *
+ * @return bool
+ */
+ public function containsOneItem()
+ {
+ return $this->count() === 1;
+ }
+
+ /**
+ * Join all items from the collection using a string. The final items can use a separate glue string.
+ *
+ * @param string $glue
+ * @param string $finalGlue
+ * @return string
+ */
+ public function join($glue, $finalGlue = '')
+ {
+ if ($finalGlue === '') {
+ return $this->implode($glue);
+ }
+
+ $count = $this->count();
+
+ if ($count === 0) {
+ return '';
+ }
+
+ if ($count === 1) {
+ return $this->last();
+ }
+
+ $collection = new static($this->items);
+
+ $finalItem = $collection->pop();
+
+ return $collection->implode($glue).$finalGlue.$finalItem;
+ }
+
+ /**
+ * Get the keys of the collection items.
+ *
+ * @return static
+ */
+ public function keys()
+ {
+ return new static(array_keys($this->items));
+ }
+
+ /**
+ * Get the last item from the collection.
+ *
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public function last(callable $callback = null, $default = null)
+ {
+ return Arr::last($this->items, $callback, $default);
+ }
+
+ /**
+ * Get the values of a given key.
+ *
+ * @param string|array|int|null $value
+ * @param string|null $key
+ * @return static
+ */
+ public function pluck($value, $key = null)
+ {
+ return new static(Arr::pluck($this->items, $value, $key));
+ }
+
+ /**
+ * Run a map over each of the items.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function map(callable $callback)
+ {
+ $keys = array_keys($this->items);
+
+ $items = array_map($callback, $this->items, $keys);
+
+ return new static(array_combine($keys, $items));
+ }
+
+ /**
+ * Run a dictionary map over the items.
+ *
+ * The callback should return an associative array with a single key/value pair.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function mapToDictionary(callable $callback)
+ {
+ $dictionary = [];
+
+ foreach ($this->items as $key => $item) {
+ $pair = $callback($item, $key);
+
+ $key = key($pair);
+
+ $value = reset($pair);
+
+ if (! isset($dictionary[$key])) {
+ $dictionary[$key] = [];
+ }
+
+ $dictionary[$key][] = $value;
+ }
+
+ return new static($dictionary);
+ }
+
+ /**
+ * Run an associative map over each of the items.
+ *
+ * The callback should return an associative array with a single key/value pair.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function mapWithKeys(callable $callback)
+ {
+ $result = [];
+
+ foreach ($this->items as $key => $value) {
+ $assoc = $callback($value, $key);
+
+ foreach ($assoc as $mapKey => $mapValue) {
+ $result[$mapKey] = $mapValue;
+ }
+ }
+
+ return new static($result);
+ }
+
+ /**
+ * Merge the collection with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function merge($items)
+ {
+ return new static(array_merge($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Recursively merge the collection with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function mergeRecursive($items)
+ {
+ return new static(array_merge_recursive($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Create a collection by using this collection for keys and another for its values.
+ *
+ * @param mixed $values
+ * @return static
+ */
+ public function combine($values)
+ {
+ return new static(array_combine($this->all(), $this->getArrayableItems($values)));
+ }
+
+ /**
+ * Union the collection with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function union($items)
+ {
+ return new static($this->items + $this->getArrayableItems($items));
+ }
+
+ /**
+ * Create a new collection consisting of every n-th element.
+ *
+ * @param int $step
+ * @param int $offset
+ * @return static
+ */
+ public function nth($step, $offset = 0)
+ {
+ $new = [];
+
+ $position = 0;
+
+ foreach ($this->slice($offset)->items as $item) {
+ if ($position % $step === 0) {
+ $new[] = $item;
+ }
+
+ $position++;
+ }
+
+ return new static($new);
+ }
+
+ /**
+ * Get the items with the specified keys.
+ *
+ * @param mixed $keys
+ * @return static
+ */
+ public function only($keys)
+ {
+ if (is_null($keys)) {
+ return new static($this->items);
+ }
+
+ if ($keys instanceof Enumerable) {
+ $keys = $keys->all();
+ }
+
+ $keys = is_array($keys) ? $keys : func_get_args();
+
+ return new static(Arr::only($this->items, $keys));
+ }
+
+ /**
+ * Get and remove the last N items from the collection.
+ *
+ * @param int $count
+ * @return mixed
+ */
+ public function pop($count = 1)
+ {
+ if ($count === 1) {
+ return array_pop($this->items);
+ }
+
+ if ($this->isEmpty()) {
+ return new static;
+ }
+
+ $results = [];
+
+ $collectionCount = $this->count();
+
+ foreach (range(1, min($count, $collectionCount)) as $item) {
+ array_push($results, array_pop($this->items));
+ }
+
+ return new static($results);
+ }
+
+ /**
+ * Push an item onto the beginning of the collection.
+ *
+ * @param mixed $value
+ * @param mixed $key
+ * @return $this
+ */
+ public function prepend($value, $key = null)
+ {
+ $this->items = Arr::prepend($this->items, ...func_get_args());
+
+ return $this;
+ }
+
+ /**
+ * Push one or more items onto the end of the collection.
+ *
+ * @param mixed $values
+ * @return $this
+ */
+ public function push(...$values)
+ {
+ foreach ($values as $value) {
+ $this->items[] = $value;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Push all of the given items onto the collection.
+ *
+ * @param iterable $source
+ * @return static
+ */
+ public function concat($source)
+ {
+ $result = new static($this);
+
+ foreach ($source as $item) {
+ $result->push($item);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get and remove an item from the collection.
+ *
+ * @param mixed $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public function pull($key, $default = null)
+ {
+ return Arr::pull($this->items, $key, $default);
+ }
+
+ /**
+ * Put an item in the collection by key.
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @return $this
+ */
+ public function put($key, $value)
+ {
+ $this->offsetSet($key, $value);
+
+ return $this;
+ }
+
+ /**
+ * Get one or a specified number of items randomly from the collection.
+ *
+ * @param int|null $number
+ * @return static|mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function random($number = null)
+ {
+ if (is_null($number)) {
+ return Arr::random($this->items);
+ }
+
+ return new static(Arr::random($this->items, $number));
+ }
+
+ /**
+ * Replace the collection items with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function replace($items)
+ {
+ return new static(array_replace($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Recursively replace the collection items with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function replaceRecursive($items)
+ {
+ return new static(array_replace_recursive($this->items, $this->getArrayableItems($items)));
+ }
+
+ /**
+ * Reverse items order.
+ *
+ * @return static
+ */
+ public function reverse()
+ {
+ return new static(array_reverse($this->items, true));
+ }
+
+ /**
+ * Search the collection for a given value and return the corresponding key if successful.
+ *
+ * @param mixed $value
+ * @param bool $strict
+ * @return mixed
+ */
+ public function search($value, $strict = false)
+ {
+ if (! $this->useAsCallable($value)) {
+ return array_search($value, $this->items, $strict);
+ }
+
+ foreach ($this->items as $key => $item) {
+ if ($value($item, $key)) {
+ return $key;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get and remove the first N items from the collection.
+ *
+ * @param int $count
+ * @return mixed
+ */
+ public function shift($count = 1)
+ {
+ if ($count === 1) {
+ return array_shift($this->items);
+ }
+
+ if ($this->isEmpty()) {
+ return new static;
+ }
+
+ $results = [];
+
+ $collectionCount = $this->count();
+
+ foreach (range(1, min($count, $collectionCount)) as $item) {
+ array_push($results, array_shift($this->items));
+ }
+
+ return new static($results);
+ }
+
+ /**
+ * Shuffle the items in the collection.
+ *
+ * @param int|null $seed
+ * @return static
+ */
+ public function shuffle($seed = null)
+ {
+ return new static(Arr::shuffle($this->items, $seed));
+ }
+
+ /**
+ * Create chunks representing a "sliding window" view of the items in the collection.
+ *
+ * @param int $size
+ * @param int $step
+ * @return static
+ */
+ public function sliding($size = 2, $step = 1)
+ {
+ $chunks = floor(($this->count() - $size) / $step) + 1;
+
+ return static::times($chunks, function ($number) use ($size, $step) {
+ return $this->slice(($number - 1) * $step, $size);
+ });
+ }
+
+ /**
+ * Skip the first {$count} items.
+ *
+ * @param int $count
+ * @return static
+ */
+ public function skip($count)
+ {
+ return $this->slice($count);
+ }
+
+ /**
+ * Skip items in the collection until the given condition is met.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function skipUntil($value)
+ {
+ return new static($this->lazy()->skipUntil($value)->all());
+ }
+
+ /**
+ * Skip items in the collection while the given condition is met.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function skipWhile($value)
+ {
+ return new static($this->lazy()->skipWhile($value)->all());
+ }
+
+ /**
+ * Slice the underlying collection array.
+ *
+ * @param int $offset
+ * @param int|null $length
+ * @return static
+ */
+ public function slice($offset, $length = null)
+ {
+ return new static(array_slice($this->items, $offset, $length, true));
+ }
+
+ /**
+ * Split a collection into a certain number of groups.
+ *
+ * @param int $numberOfGroups
+ * @return static
+ */
+ public function split($numberOfGroups)
+ {
+ if ($this->isEmpty()) {
+ return new static;
+ }
+
+ $groups = new static;
+
+ $groupSize = floor($this->count() / $numberOfGroups);
+
+ $remain = $this->count() % $numberOfGroups;
+
+ $start = 0;
+
+ for ($i = 0; $i < $numberOfGroups; $i++) {
+ $size = $groupSize;
+
+ if ($i < $remain) {
+ $size++;
+ }
+
+ if ($size) {
+ $groups->push(new static(array_slice($this->items, $start, $size)));
+
+ $start += $size;
+ }
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Split a collection into a certain number of groups, and fill the first groups completely.
+ *
+ * @param int $numberOfGroups
+ * @return static
+ */
+ public function splitIn($numberOfGroups)
+ {
+ return $this->chunk(ceil($this->count() / $numberOfGroups));
+ }
+
+ /**
+ * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return mixed
+ *
+ * @throws \Tightenco\Collect\Support\ItemNotFoundException
+ * @throws \Tightenco\Collect\Support\MultipleItemsFoundException
+ */
+ public function sole($key = null, $operator = null, $value = null)
+ {
+ $filter = func_num_args() > 1
+ ? $this->operatorForWhere(...func_get_args())
+ : $key;
+
+ $items = $this->when($filter)->filter($filter);
+
+ if ($items->isEmpty()) {
+ throw new ItemNotFoundException;
+ }
+
+ if ($items->count() > 1) {
+ throw new MultipleItemsFoundException;
+ }
+
+ return $items->first();
+ }
+
+ /**
+ * Get the first item in the collection but throw an exception if no matching items exist.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return mixed
+ *
+ * @throws \Tightenco\Collect\Support\ItemNotFoundException
+ */
+ public function firstOrFail($key = null, $operator = null, $value = null)
+ {
+ $filter = func_num_args() > 1
+ ? $this->operatorForWhere(...func_get_args())
+ : $key;
+
+ $placeholder = new stdClass();
+
+ $item = $this->first($filter, $placeholder);
+
+ if ($item === $placeholder) {
+ throw new ItemNotFoundException;
+ }
+
+ return $item;
+ }
+
+ /**
+ * Chunk the collection into chunks of the given size.
+ *
+ * @param int $size
+ * @return static
+ */
+ public function chunk($size)
+ {
+ if ($size <= 0) {
+ return new static;
+ }
+
+ $chunks = [];
+
+ foreach (array_chunk($this->items, $size, true) as $chunk) {
+ $chunks[] = new static($chunk);
+ }
+
+ return new static($chunks);
+ }
+
+ /**
+ * Chunk the collection into chunks with a callback.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function chunkWhile(callable $callback)
+ {
+ return new static(
+ $this->lazy()->chunkWhile($callback)->mapInto(static::class)
+ );
+ }
+
+ /**
+ * Sort through each item with a callback.
+ *
+ * @param callable|int|null $callback
+ * @return static
+ */
+ public function sort($callback = null)
+ {
+ $items = $this->items;
+
+ $callback && is_callable($callback)
+ ? uasort($items, $callback)
+ : asort($items, $callback ?? SORT_REGULAR);
+
+ return new static($items);
+ }
+
+ /**
+ * Sort items in descending order.
+ *
+ * @param int $options
+ * @return static
+ */
+ public function sortDesc($options = SORT_REGULAR)
+ {
+ $items = $this->items;
+
+ arsort($items, $options);
+
+ return new static($items);
+ }
+
+ /**
+ * Sort the collection using the given callback.
+ *
+ * @param callable|array|string $callback
+ * @param int $options
+ * @param bool $descending
+ * @return static
+ */
+ public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
+ {
+ if (is_array($callback) && ! is_callable($callback)) {
+ return $this->sortByMany($callback);
+ }
+
+ $results = [];
+
+ $callback = $this->valueRetriever($callback);
+
+ // First we will loop through the items and get the comparator from a callback
+ // function which we were given. Then, we will sort the returned values and
+ // grab all the corresponding values for the sorted keys from this array.
+ foreach ($this->items as $key => $value) {
+ $results[$key] = $callback($value, $key);
+ }
+
+ $descending ? arsort($results, $options)
+ : asort($results, $options);
+
+ // Once we have sorted all of the keys in the array, we will loop through them
+ // and grab the corresponding model so we can set the underlying items list
+ // to the sorted version. Then we'll just return the collection instance.
+ foreach (array_keys($results) as $key) {
+ $results[$key] = $this->items[$key];
+ }
+
+ return new static($results);
+ }
+
+ /**
+ * Sort the collection using multiple comparisons.
+ *
+ * @param array $comparisons
+ * @return static
+ */
+ protected function sortByMany(array $comparisons = [])
+ {
+ $items = $this->items;
+
+ usort($items, function ($a, $b) use ($comparisons) {
+ foreach ($comparisons as $comparison) {
+ $comparison = Arr::wrap($comparison);
+
+ $prop = $comparison[0];
+
+ $ascending = Arr::get($comparison, 1, true) === true ||
+ Arr::get($comparison, 1, true) === 'asc';
+
+ $result = 0;
+
+ if (! is_string($prop) && is_callable($prop)) {
+ $result = $prop($a, $b);
+ } else {
+ $values = [data_get($a, $prop), data_get($b, $prop)];
+
+ if (! $ascending) {
+ $values = array_reverse($values);
+ }
+
+ $result = $values[0] <=> $values[1];
+ }
+
+ if ($result === 0) {
+ continue;
+ }
+
+ return $result;
+ }
+ });
+
+ return new static($items);
+ }
+
+ /**
+ * Sort the collection in descending order using the given callback.
+ *
+ * @param callable|string $callback
+ * @param int $options
+ * @return static
+ */
+ public function sortByDesc($callback, $options = SORT_REGULAR)
+ {
+ return $this->sortBy($callback, $options, true);
+ }
+
+ /**
+ * Sort the collection keys.
+ *
+ * @param int $options
+ * @param bool $descending
+ * @return static
+ */
+ public function sortKeys($options = SORT_REGULAR, $descending = false)
+ {
+ $items = $this->items;
+
+ $descending ? krsort($items, $options) : ksort($items, $options);
+
+ return new static($items);
+ }
+
+ /**
+ * Sort the collection keys in descending order.
+ *
+ * @param int $options
+ * @return static
+ */
+ public function sortKeysDesc($options = SORT_REGULAR)
+ {
+ return $this->sortKeys($options, true);
+ }
+
+ /**
+ * Sort the collection keys using a callback.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function sortKeysUsing(callable $callback)
+ {
+ $items = $this->items;
+
+ uksort($items, $callback);
+
+ return new static($items);
+ }
+
+ /**
+ * Splice a portion of the underlying collection array.
+ *
+ * @param int $offset
+ * @param int|null $length
+ * @param mixed $replacement
+ * @return static
+ */
+ public function splice($offset, $length = null, $replacement = [])
+ {
+ if (func_num_args() === 1) {
+ return new static(array_splice($this->items, $offset));
+ }
+
+ return new static(array_splice($this->items, $offset, $length, $this->getArrayableItems($replacement)));
+ }
+
+ /**
+ * Take the first or last {$limit} items.
+ *
+ * @param int $limit
+ * @return static
+ */
+ public function take($limit)
+ {
+ if ($limit < 0) {
+ return $this->slice($limit, abs($limit));
+ }
+
+ return $this->slice(0, $limit);
+ }
+
+ /**
+ * Take items in the collection until the given condition is met.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function takeUntil($value)
+ {
+ return new static($this->lazy()->takeUntil($value)->all());
+ }
+
+ /**
+ * Take items in the collection while the given condition is met.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function takeWhile($value)
+ {
+ return new static($this->lazy()->takeWhile($value)->all());
+ }
+
+ /**
+ * Transform each item in the collection using a callback.
+ *
+ * @param callable $callback
+ * @return $this
+ */
+ public function transform(callable $callback)
+ {
+ $this->items = $this->map($callback)->all();
+
+ return $this;
+ }
+
+ /**
+ * Convert a flatten "dot" notation array into an expanded array.
+ *
+ * @return static
+ */
+ public function undot()
+ {
+ return new static(Arr::undot($this->all()));
+ }
+
+ /**
+ * Return only unique items from the collection array.
+ *
+ * @param string|callable|null $key
+ * @param bool $strict
+ * @return static
+ */
+ public function unique($key = null, $strict = false)
+ {
+ if (is_null($key) && $strict === false) {
+ return new static(array_unique($this->items, SORT_REGULAR));
+ }
+
+ $callback = $this->valueRetriever($key);
+
+ $exists = [];
+
+ return $this->reject(function ($item, $key) use ($callback, $strict, &$exists) {
+ if (in_array($id = $callback($item, $key), $exists, $strict)) {
+ return true;
+ }
+
+ $exists[] = $id;
+ });
+ }
+
+ /**
+ * Reset the keys on the underlying array.
+ *
+ * @return static
+ */
+ public function values()
+ {
+ return new static(array_values($this->items));
+ }
+
+ /**
+ * Zip the collection together with one or more arrays.
+ *
+ * e.g. new Collection([1, 2, 3])->zip([4, 5, 6]);
+ * => [[1, 4], [2, 5], [3, 6]]
+ *
+ * @param mixed ...$items
+ * @return static
+ */
+ public function zip($items)
+ {
+ $arrayableItems = array_map(function ($items) {
+ return $this->getArrayableItems($items);
+ }, func_get_args());
+
+ $params = array_merge([function () {
+ return new static(func_get_args());
+ }, $this->items], $arrayableItems);
+
+ return new static(array_map(...$params));
+ }
+
+ /**
+ * Pad collection to the specified length with a value.
+ *
+ * @param int $size
+ * @param mixed $value
+ * @return static
+ */
+ public function pad($size, $value)
+ {
+ return new static(array_pad($this->items, $size, $value));
+ }
+
+ /**
+ * Get an iterator for the items.
+ *
+ * @return \ArrayIterator
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ return new ArrayIterator($this->items);
+ }
+
+ /**
+ * Count the number of items in the collection.
+ *
+ * @return int
+ */
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ /**
+ * Count the number of items in the collection by a field or using a callback.
+ *
+ * @param callable|string $countBy
+ * @return static
+ */
+ public function countBy($countBy = null)
+ {
+ return new static($this->lazy()->countBy($countBy)->all());
+ }
+
+ /**
+ * Add an item to the collection.
+ *
+ * @param mixed $item
+ * @return $this
+ */
+ public function add($item)
+ {
+ $this->items[] = $item;
+
+ return $this;
+ }
+
+ /**
+ * Get a base Support collection instance from this collection.
+ *
+ * @return \Tightenco\Collect\Support\Collection
+ */
+ public function toBase()
+ {
+ return new self($this);
+ }
+
+ /**
+ * Determine if an item exists at an offset.
+ *
+ * @param mixed $key
+ * @return bool
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetExists($key)
+ {
+ return isset($this->items[$key]);
+ }
+
+ /**
+ * Get an item at a given offset.
+ *
+ * @param mixed $key
+ * @return mixed
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetGet($key)
+ {
+ return $this->items[$key];
+ }
+
+ /**
+ * Set the item at a given offset.
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetSet($key, $value)
+ {
+ if (is_null($key)) {
+ $this->items[] = $value;
+ } else {
+ $this->items[$key] = $value;
+ }
+ }
+
+ /**
+ * Unset the item at a given offset.
+ *
+ * @param mixed $key
+ * @return void
+ */
+ #[\ReturnTypeWillChange]
+ public function offsetUnset($key)
+ {
+ unset($this->items[$key]);
+ }
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/Enumerable.php b/vendor/tightenco/collect/src/Collect/Support/Enumerable.php
new file mode 100644
index 0000000..60af386
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/Enumerable.php
@@ -0,0 +1,1027 @@
+zip([4, 5, 6]);
+ * => [[1, 4], [2, 5], [3, 6]]
+ *
+ * @param mixed ...$items
+ * @return static
+ */
+ public function zip($items);
+
+ /**
+ * Collect the values into a collection.
+ *
+ * @return \Tightenco\Collect\Support\Collection
+ */
+ public function collect();
+
+ /**
+ * Convert the collection to its string representation.
+ *
+ * @return string
+ */
+ public function __toString();
+
+ /**
+ * Add a method to the list of proxied methods.
+ *
+ * @param string $method
+ * @return void
+ */
+ public static function proxy($method);
+
+ /**
+ * Dynamically access collection proxies.
+ *
+ * @param string $key
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function __get($key);
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php b/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php
new file mode 100644
index 0000000..01ac43f
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/HigherOrderCollectionProxy.php
@@ -0,0 +1,63 @@
+method = $method;
+ $this->collection = $collection;
+ }
+
+ /**
+ * Proxy accessing an attribute onto the collection items.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ return $this->collection->{$this->method}(function ($value) use ($key) {
+ return is_array($value) ? $value[$key] : $value->{$key};
+ });
+ }
+
+ /**
+ * Proxy a method call onto the collection items.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ return $this->collection->{$this->method}(function ($value) use ($method, $parameters) {
+ return $value->{$method}(...$parameters);
+ });
+ }
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/HigherOrderWhenProxy.php b/vendor/tightenco/collect/src/Collect/Support/HigherOrderWhenProxy.php
new file mode 100644
index 0000000..ea48c7c
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/HigherOrderWhenProxy.php
@@ -0,0 +1,63 @@
+condition = $condition;
+ $this->collection = $collection;
+ }
+
+ /**
+ * Proxy accessing an attribute onto the collection.
+ *
+ * @param string $key
+ * @return mixed
+ */
+ public function __get($key)
+ {
+ return $this->condition
+ ? $this->collection->{$key}
+ : $this->collection;
+ }
+
+ /**
+ * Proxy a method call onto the collection.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ */
+ public function __call($method, $parameters)
+ {
+ return $this->condition
+ ? $this->collection->{$method}(...$parameters)
+ : $this->collection;
+ }
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/LazyCollection.php b/vendor/tightenco/collect/src/Collect/Support/LazyCollection.php
new file mode 100644
index 0000000..a54787d
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/LazyCollection.php
@@ -0,0 +1,1585 @@
+source = $source;
+ } elseif (is_null($source)) {
+ $this->source = static::empty();
+ } else {
+ $this->source = $this->getArrayableItems($source);
+ }
+ }
+
+ /**
+ * Create a collection with the given range.
+ *
+ * @param int $from
+ * @param int $to
+ * @return static
+ */
+ public static function range($from, $to)
+ {
+ return new static(function () use ($from, $to) {
+ if ($from <= $to) {
+ for (; $from <= $to; $from++) {
+ yield $from;
+ }
+ } else {
+ for (; $from >= $to; $from--) {
+ yield $from;
+ }
+ }
+ });
+ }
+
+ /**
+ * Get all items in the enumerable.
+ *
+ * @return array
+ */
+ public function all()
+ {
+ if (is_array($this->source)) {
+ return $this->source;
+ }
+
+ return iterator_to_array($this->getIterator());
+ }
+
+ /**
+ * Eager load all items into a new lazy collection backed by an array.
+ *
+ * @return static
+ */
+ public function eager()
+ {
+ return new static($this->all());
+ }
+
+ /**
+ * Cache values as they're enumerated.
+ *
+ * @return static
+ */
+ public function remember()
+ {
+ $iterator = $this->getIterator();
+
+ $iteratorIndex = 0;
+
+ $cache = [];
+
+ return new static(function () use ($iterator, &$iteratorIndex, &$cache) {
+ for ($index = 0; true; $index++) {
+ if (array_key_exists($index, $cache)) {
+ yield $cache[$index][0] => $cache[$index][1];
+
+ continue;
+ }
+
+ if ($iteratorIndex < $index) {
+ $iterator->next();
+
+ $iteratorIndex++;
+ }
+
+ if (! $iterator->valid()) {
+ break;
+ }
+
+ $cache[$index] = [$iterator->key(), $iterator->current()];
+
+ yield $cache[$index][0] => $cache[$index][1];
+ }
+ });
+ }
+
+ /**
+ * Get the average value of a given key.
+ *
+ * @param callable|string|null $callback
+ * @return mixed
+ */
+ public function avg($callback = null)
+ {
+ return $this->collect()->avg($callback);
+ }
+
+ /**
+ * Get the median of a given key.
+ *
+ * @param string|array|null $key
+ * @return mixed
+ */
+ public function median($key = null)
+ {
+ return $this->collect()->median($key);
+ }
+
+ /**
+ * Get the mode of a given key.
+ *
+ * @param string|array|null $key
+ * @return array|null
+ */
+ public function mode($key = null)
+ {
+ return $this->collect()->mode($key);
+ }
+
+ /**
+ * Collapse the collection of items into a single array.
+ *
+ * @return static
+ */
+ public function collapse()
+ {
+ return new static(function () {
+ foreach ($this as $values) {
+ if (is_array($values) || $values instanceof Enumerable) {
+ foreach ($values as $value) {
+ yield $value;
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Determine if an item exists in the enumerable.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return bool
+ */
+ public function contains($key, $operator = null, $value = null)
+ {
+ if (func_num_args() === 1 && $this->useAsCallable($key)) {
+ $placeholder = new stdClass;
+
+ return $this->first($key, $placeholder) !== $placeholder;
+ }
+
+ if (func_num_args() === 1) {
+ $needle = $key;
+
+ foreach ($this as $value) {
+ if ($value == $needle) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return $this->contains($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Determine if an item is not contained in the enumerable.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return bool
+ */
+ public function doesntContain($key, $operator = null, $value = null)
+ {
+ return ! $this->contains(...func_get_args());
+ }
+
+ /**
+ * Cross join the given iterables, returning all possible permutations.
+ *
+ * @param array ...$arrays
+ * @return static
+ */
+ public function crossJoin(...$arrays)
+ {
+ return $this->passthru('crossJoin', func_get_args());
+ }
+
+ /**
+ * Count the number of items in the collection by a field or using a callback.
+ *
+ * @param callable|string $countBy
+ * @return static
+ */
+ public function countBy($countBy = null)
+ {
+ $countBy = is_null($countBy)
+ ? $this->identity()
+ : $this->valueRetriever($countBy);
+
+ return new static(function () use ($countBy) {
+ $counts = [];
+
+ foreach ($this as $key => $value) {
+ $group = $countBy($value, $key);
+
+ if (empty($counts[$group])) {
+ $counts[$group] = 0;
+ }
+
+ $counts[$group]++;
+ }
+
+ yield from $counts;
+ });
+ }
+
+ /**
+ * Get the items that are not present in the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function diff($items)
+ {
+ return $this->passthru('diff', func_get_args());
+ }
+
+ /**
+ * Get the items that are not present in the given items, using the callback.
+ *
+ * @param mixed $items
+ * @param callable $callback
+ * @return static
+ */
+ public function diffUsing($items, callable $callback)
+ {
+ return $this->passthru('diffUsing', func_get_args());
+ }
+
+ /**
+ * Get the items whose keys and values are not present in the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function diffAssoc($items)
+ {
+ return $this->passthru('diffAssoc', func_get_args());
+ }
+
+ /**
+ * Get the items whose keys and values are not present in the given items, using the callback.
+ *
+ * @param mixed $items
+ * @param callable $callback
+ * @return static
+ */
+ public function diffAssocUsing($items, callable $callback)
+ {
+ return $this->passthru('diffAssocUsing', func_get_args());
+ }
+
+ /**
+ * Get the items whose keys are not present in the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function diffKeys($items)
+ {
+ return $this->passthru('diffKeys', func_get_args());
+ }
+
+ /**
+ * Get the items whose keys are not present in the given items, using the callback.
+ *
+ * @param mixed $items
+ * @param callable $callback
+ * @return static
+ */
+ public function diffKeysUsing($items, callable $callback)
+ {
+ return $this->passthru('diffKeysUsing', func_get_args());
+ }
+
+ /**
+ * Retrieve duplicate items.
+ *
+ * @param callable|string|null $callback
+ * @param bool $strict
+ * @return static
+ */
+ public function duplicates($callback = null, $strict = false)
+ {
+ return $this->passthru('duplicates', func_get_args());
+ }
+
+ /**
+ * Retrieve duplicate items using strict comparison.
+ *
+ * @param callable|string|null $callback
+ * @return static
+ */
+ public function duplicatesStrict($callback = null)
+ {
+ return $this->passthru('duplicatesStrict', func_get_args());
+ }
+
+ /**
+ * Get all items except for those with the specified keys.
+ *
+ * @param mixed $keys
+ * @return static
+ */
+ public function except($keys)
+ {
+ return $this->passthru('except', func_get_args());
+ }
+
+ /**
+ * Run a filter over each of the items.
+ *
+ * @param callable|null $callback
+ * @return static
+ */
+ public function filter(callable $callback = null)
+ {
+ if (is_null($callback)) {
+ $callback = function ($value) {
+ return (bool) $value;
+ };
+ }
+
+ return new static(function () use ($callback) {
+ foreach ($this as $key => $value) {
+ if ($callback($value, $key)) {
+ yield $key => $value;
+ }
+ }
+ });
+ }
+
+ /**
+ * Get the first item from the enumerable passing the given truth test.
+ *
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public function first(callable $callback = null, $default = null)
+ {
+ $iterator = $this->getIterator();
+
+ if (is_null($callback)) {
+ if (! $iterator->valid()) {
+ return value($default);
+ }
+
+ return $iterator->current();
+ }
+
+ foreach ($iterator as $key => $value) {
+ if ($callback($value, $key)) {
+ return $value;
+ }
+ }
+
+ return value($default);
+ }
+
+ /**
+ * Get a flattened list of the items in the collection.
+ *
+ * @param int $depth
+ * @return static
+ */
+ public function flatten($depth = INF)
+ {
+ $instance = new static(function () use ($depth) {
+ foreach ($this as $item) {
+ if (! is_array($item) && ! $item instanceof Enumerable) {
+ yield $item;
+ } elseif ($depth === 1) {
+ yield from $item;
+ } else {
+ yield from (new static($item))->flatten($depth - 1);
+ }
+ }
+ });
+
+ return $instance->values();
+ }
+
+ /**
+ * Flip the items in the collection.
+ *
+ * @return static
+ */
+ public function flip()
+ {
+ return new static(function () {
+ foreach ($this as $key => $value) {
+ yield $value => $key;
+ }
+ });
+ }
+
+ /**
+ * Get an item by key.
+ *
+ * @param mixed $key
+ * @param mixed $default
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if (is_null($key)) {
+ return;
+ }
+
+ foreach ($this as $outerKey => $outerValue) {
+ if ($outerKey == $key) {
+ return $outerValue;
+ }
+ }
+
+ return value($default);
+ }
+
+ /**
+ * Group an associative array by a field or using a callback.
+ *
+ * @param array|callable|string $groupBy
+ * @param bool $preserveKeys
+ * @return static
+ */
+ public function groupBy($groupBy, $preserveKeys = false)
+ {
+ return $this->passthru('groupBy', func_get_args());
+ }
+
+ /**
+ * Key an associative array by a field or using a callback.
+ *
+ * @param callable|string $keyBy
+ * @return static
+ */
+ public function keyBy($keyBy)
+ {
+ return new static(function () use ($keyBy) {
+ $keyBy = $this->valueRetriever($keyBy);
+
+ foreach ($this as $key => $item) {
+ $resolvedKey = $keyBy($item, $key);
+
+ if (is_object($resolvedKey)) {
+ $resolvedKey = (string) $resolvedKey;
+ }
+
+ yield $resolvedKey => $item;
+ }
+ });
+ }
+
+ /**
+ * Determine if an item exists in the collection by key.
+ *
+ * @param mixed $key
+ * @return bool
+ */
+ public function has($key)
+ {
+ $keys = array_flip(is_array($key) ? $key : func_get_args());
+ $count = count($keys);
+
+ foreach ($this as $key => $value) {
+ if (array_key_exists($key, $keys) && --$count == 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determine if any of the keys exist in the collection.
+ *
+ * @param mixed $key
+ * @return bool
+ */
+ public function hasAny($key)
+ {
+ $keys = array_flip(is_array($key) ? $key : func_get_args());
+
+ foreach ($this as $key => $value) {
+ if (array_key_exists($key, $keys)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Concatenate values of a given key as a string.
+ *
+ * @param string $value
+ * @param string|null $glue
+ * @return string
+ */
+ public function implode($value, $glue = null)
+ {
+ return $this->collect()->implode(...func_get_args());
+ }
+
+ /**
+ * Intersect the collection with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function intersect($items)
+ {
+ return $this->passthru('intersect', func_get_args());
+ }
+
+ /**
+ * Intersect the collection with the given items by key.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function intersectByKeys($items)
+ {
+ return $this->passthru('intersectByKeys', func_get_args());
+ }
+
+ /**
+ * Determine if the items are empty or not.
+ *
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return ! $this->getIterator()->valid();
+ }
+
+ /**
+ * Determine if the collection contains a single item.
+ *
+ * @return bool
+ */
+ public function containsOneItem()
+ {
+ return $this->take(2)->count() === 1;
+ }
+
+ /**
+ * Join all items from the collection using a string. The final items can use a separate glue string.
+ *
+ * @param string $glue
+ * @param string $finalGlue
+ * @return string
+ */
+ public function join($glue, $finalGlue = '')
+ {
+ return $this->collect()->join(...func_get_args());
+ }
+
+ /**
+ * Get the keys of the collection items.
+ *
+ * @return static
+ */
+ public function keys()
+ {
+ return new static(function () {
+ foreach ($this as $key => $value) {
+ yield $key;
+ }
+ });
+ }
+
+ /**
+ * Get the last item from the collection.
+ *
+ * @param callable|null $callback
+ * @param mixed $default
+ * @return mixed
+ */
+ public function last(callable $callback = null, $default = null)
+ {
+ $needle = $placeholder = new stdClass;
+
+ foreach ($this as $key => $value) {
+ if (is_null($callback) || $callback($value, $key)) {
+ $needle = $value;
+ }
+ }
+
+ return $needle === $placeholder ? value($default) : $needle;
+ }
+
+ /**
+ * Get the values of a given key.
+ *
+ * @param string|array $value
+ * @param string|null $key
+ * @return static
+ */
+ public function pluck($value, $key = null)
+ {
+ return new static(function () use ($value, $key) {
+ [$value, $key] = $this->explodePluckParameters($value, $key);
+
+ foreach ($this as $item) {
+ $itemValue = data_get($item, $value);
+
+ if (is_null($key)) {
+ yield $itemValue;
+ } else {
+ $itemKey = data_get($item, $key);
+
+ if (is_object($itemKey) && method_exists($itemKey, '__toString')) {
+ $itemKey = (string) $itemKey;
+ }
+
+ yield $itemKey => $itemValue;
+ }
+ }
+ });
+ }
+
+ /**
+ * Run a map over each of the items.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function map(callable $callback)
+ {
+ return new static(function () use ($callback) {
+ foreach ($this as $key => $value) {
+ yield $key => $callback($value, $key);
+ }
+ });
+ }
+
+ /**
+ * Run a dictionary map over the items.
+ *
+ * The callback should return an associative array with a single key/value pair.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function mapToDictionary(callable $callback)
+ {
+ return $this->passthru('mapToDictionary', func_get_args());
+ }
+
+ /**
+ * Run an associative map over each of the items.
+ *
+ * The callback should return an associative array with a single key/value pair.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function mapWithKeys(callable $callback)
+ {
+ return new static(function () use ($callback) {
+ foreach ($this as $key => $value) {
+ yield from $callback($value, $key);
+ }
+ });
+ }
+
+ /**
+ * Merge the collection with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function merge($items)
+ {
+ return $this->passthru('merge', func_get_args());
+ }
+
+ /**
+ * Recursively merge the collection with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function mergeRecursive($items)
+ {
+ return $this->passthru('mergeRecursive', func_get_args());
+ }
+
+ /**
+ * Create a collection by using this collection for keys and another for its values.
+ *
+ * @param mixed $values
+ * @return static
+ */
+ public function combine($values)
+ {
+ return new static(function () use ($values) {
+ $values = $this->makeIterator($values);
+
+ $errorMessage = 'Both parameters should have an equal number of elements';
+
+ foreach ($this as $key) {
+ if (! $values->valid()) {
+ trigger_error($errorMessage, E_USER_WARNING);
+
+ break;
+ }
+
+ yield $key => $values->current();
+
+ $values->next();
+ }
+
+ if ($values->valid()) {
+ trigger_error($errorMessage, E_USER_WARNING);
+ }
+ });
+ }
+
+ /**
+ * Union the collection with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function union($items)
+ {
+ return $this->passthru('union', func_get_args());
+ }
+
+ /**
+ * Create a new collection consisting of every n-th element.
+ *
+ * @param int $step
+ * @param int $offset
+ * @return static
+ */
+ public function nth($step, $offset = 0)
+ {
+ return new static(function () use ($step, $offset) {
+ $position = 0;
+
+ foreach ($this->slice($offset) as $item) {
+ if ($position % $step === 0) {
+ yield $item;
+ }
+
+ $position++;
+ }
+ });
+ }
+
+ /**
+ * Get the items with the specified keys.
+ *
+ * @param mixed $keys
+ * @return static
+ */
+ public function only($keys)
+ {
+ if ($keys instanceof Enumerable) {
+ $keys = $keys->all();
+ } elseif (! is_null($keys)) {
+ $keys = is_array($keys) ? $keys : func_get_args();
+ }
+
+ return new static(function () use ($keys) {
+ if (is_null($keys)) {
+ yield from $this;
+ } else {
+ $keys = array_flip($keys);
+
+ foreach ($this as $key => $value) {
+ if (array_key_exists($key, $keys)) {
+ yield $key => $value;
+
+ unset($keys[$key]);
+
+ if (empty($keys)) {
+ break;
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Push all of the given items onto the collection.
+ *
+ * @param iterable $source
+ * @return static
+ */
+ public function concat($source)
+ {
+ return (new static(function () use ($source) {
+ yield from $this;
+ yield from $source;
+ }))->values();
+ }
+
+ /**
+ * Get one or a specified number of items randomly from the collection.
+ *
+ * @param int|null $number
+ * @return static|mixed
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function random($number = null)
+ {
+ $result = $this->collect()->random(...func_get_args());
+
+ return is_null($number) ? $result : new static($result);
+ }
+
+ /**
+ * Replace the collection items with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function replace($items)
+ {
+ return new static(function () use ($items) {
+ $items = $this->getArrayableItems($items);
+
+ foreach ($this as $key => $value) {
+ if (array_key_exists($key, $items)) {
+ yield $key => $items[$key];
+
+ unset($items[$key]);
+ } else {
+ yield $key => $value;
+ }
+ }
+
+ foreach ($items as $key => $value) {
+ yield $key => $value;
+ }
+ });
+ }
+
+ /**
+ * Recursively replace the collection items with the given items.
+ *
+ * @param mixed $items
+ * @return static
+ */
+ public function replaceRecursive($items)
+ {
+ return $this->passthru('replaceRecursive', func_get_args());
+ }
+
+ /**
+ * Reverse items order.
+ *
+ * @return static
+ */
+ public function reverse()
+ {
+ return $this->passthru('reverse', func_get_args());
+ }
+
+ /**
+ * Search the collection for a given value and return the corresponding key if successful.
+ *
+ * @param mixed $value
+ * @param bool $strict
+ * @return mixed
+ */
+ public function search($value, $strict = false)
+ {
+ $predicate = $this->useAsCallable($value)
+ ? $value
+ : function ($item) use ($value, $strict) {
+ return $strict ? $item === $value : $item == $value;
+ };
+
+ foreach ($this as $key => $item) {
+ if ($predicate($item, $key)) {
+ return $key;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Shuffle the items in the collection.
+ *
+ * @param int|null $seed
+ * @return static
+ */
+ public function shuffle($seed = null)
+ {
+ return $this->passthru('shuffle', func_get_args());
+ }
+
+ /**
+ * Create chunks representing a "sliding window" view of the items in the collection.
+ *
+ * @param int $size
+ * @param int $step
+ * @return static
+ */
+ public function sliding($size = 2, $step = 1)
+ {
+ return new static(function () use ($size, $step) {
+ $iterator = $this->getIterator();
+
+ $chunk = [];
+
+ while ($iterator->valid()) {
+ $chunk[$iterator->key()] = $iterator->current();
+
+ if (count($chunk) == $size) {
+ yield tap(new static($chunk), function () use (&$chunk, $step) {
+ $chunk = array_slice($chunk, $step, null, true);
+ });
+
+ // If the $step between chunks is bigger than each chunk's $size
+ // we will skip the extra items (which should never be in any
+ // chunk) before we continue to the next chunk in the loop.
+ if ($step > $size) {
+ $skip = $step - $size;
+
+ for ($i = 0; $i < $skip && $iterator->valid(); $i++) {
+ $iterator->next();
+ }
+ }
+ }
+
+ $iterator->next();
+ }
+ });
+ }
+
+ /**
+ * Skip the first {$count} items.
+ *
+ * @param int $count
+ * @return static
+ */
+ public function skip($count)
+ {
+ return new static(function () use ($count) {
+ $iterator = $this->getIterator();
+
+ while ($iterator->valid() && $count--) {
+ $iterator->next();
+ }
+
+ while ($iterator->valid()) {
+ yield $iterator->key() => $iterator->current();
+
+ $iterator->next();
+ }
+ });
+ }
+
+ /**
+ * Skip items in the collection until the given condition is met.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function skipUntil($value)
+ {
+ $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
+
+ return $this->skipWhile($this->negate($callback));
+ }
+
+ /**
+ * Skip items in the collection while the given condition is met.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function skipWhile($value)
+ {
+ $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
+
+ return new static(function () use ($callback) {
+ $iterator = $this->getIterator();
+
+ while ($iterator->valid() && $callback($iterator->current(), $iterator->key())) {
+ $iterator->next();
+ }
+
+ while ($iterator->valid()) {
+ yield $iterator->key() => $iterator->current();
+
+ $iterator->next();
+ }
+ });
+ }
+
+ /**
+ * Get a slice of items from the enumerable.
+ *
+ * @param int $offset
+ * @param int|null $length
+ * @return static
+ */
+ public function slice($offset, $length = null)
+ {
+ if ($offset < 0 || $length < 0) {
+ return $this->passthru('slice', func_get_args());
+ }
+
+ $instance = $this->skip($offset);
+
+ return is_null($length) ? $instance : $instance->take($length);
+ }
+
+ /**
+ * Split a collection into a certain number of groups.
+ *
+ * @param int $numberOfGroups
+ * @return static
+ */
+ public function split($numberOfGroups)
+ {
+ return $this->passthru('split', func_get_args());
+ }
+
+ /**
+ * Get the first item in the collection, but only if exactly one item exists. Otherwise, throw an exception.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return mixed
+ *
+ * @throws \Tightenco\Collect\Support\ItemNotFoundException
+ * @throws \Tightenco\Collect\Support\MultipleItemsFoundException
+ */
+ public function sole($key = null, $operator = null, $value = null)
+ {
+ $filter = func_num_args() > 1
+ ? $this->operatorForWhere(...func_get_args())
+ : $key;
+
+ return $this
+ ->when($filter)
+ ->filter($filter)
+ ->take(2)
+ ->collect()
+ ->sole();
+ }
+
+ /**
+ * Get the first item in the collection but throw an exception if no matching items exist.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return mixed
+ *
+ * @throws \Tightenco\Collect\Support\ItemNotFoundException
+ */
+ public function firstOrFail($key = null, $operator = null, $value = null)
+ {
+ $filter = func_num_args() > 1
+ ? $this->operatorForWhere(...func_get_args())
+ : $key;
+
+ return $this
+ ->when($filter)
+ ->filter($filter)
+ ->take(1)
+ ->collect()
+ ->firstOrFail();
+ }
+
+ /**
+ * Chunk the collection into chunks of the given size.
+ *
+ * @param int $size
+ * @return static
+ */
+ public function chunk($size)
+ {
+ if ($size <= 0) {
+ return static::empty();
+ }
+
+ return new static(function () use ($size) {
+ $iterator = $this->getIterator();
+
+ while ($iterator->valid()) {
+ $chunk = [];
+
+ while (true) {
+ $chunk[$iterator->key()] = $iterator->current();
+
+ if (count($chunk) < $size) {
+ $iterator->next();
+
+ if (! $iterator->valid()) {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ yield new static($chunk);
+
+ $iterator->next();
+ }
+ });
+ }
+
+ /**
+ * Split a collection into a certain number of groups, and fill the first groups completely.
+ *
+ * @param int $numberOfGroups
+ * @return static
+ */
+ public function splitIn($numberOfGroups)
+ {
+ return $this->chunk(ceil($this->count() / $numberOfGroups));
+ }
+
+ /**
+ * Chunk the collection into chunks with a callback.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function chunkWhile(callable $callback)
+ {
+ return new static(function () use ($callback) {
+ $iterator = $this->getIterator();
+
+ $chunk = new Collection;
+
+ if ($iterator->valid()) {
+ $chunk[$iterator->key()] = $iterator->current();
+
+ $iterator->next();
+ }
+
+ while ($iterator->valid()) {
+ if (! $callback($iterator->current(), $iterator->key(), $chunk)) {
+ yield new static($chunk);
+
+ $chunk = new Collection;
+ }
+
+ $chunk[$iterator->key()] = $iterator->current();
+
+ $iterator->next();
+ }
+
+ if ($chunk->isNotEmpty()) {
+ yield new static($chunk);
+ }
+ });
+ }
+
+ /**
+ * Sort through each item with a callback.
+ *
+ * @param callable|null|int $callback
+ * @return static
+ */
+ public function sort($callback = null)
+ {
+ return $this->passthru('sort', func_get_args());
+ }
+
+ /**
+ * Sort items in descending order.
+ *
+ * @param int $options
+ * @return static
+ */
+ public function sortDesc($options = SORT_REGULAR)
+ {
+ return $this->passthru('sortDesc', func_get_args());
+ }
+
+ /**
+ * Sort the collection using the given callback.
+ *
+ * @param callable|string $callback
+ * @param int $options
+ * @param bool $descending
+ * @return static
+ */
+ public function sortBy($callback, $options = SORT_REGULAR, $descending = false)
+ {
+ return $this->passthru('sortBy', func_get_args());
+ }
+
+ /**
+ * Sort the collection in descending order using the given callback.
+ *
+ * @param callable|string $callback
+ * @param int $options
+ * @return static
+ */
+ public function sortByDesc($callback, $options = SORT_REGULAR)
+ {
+ return $this->passthru('sortByDesc', func_get_args());
+ }
+
+ /**
+ * Sort the collection keys.
+ *
+ * @param int $options
+ * @param bool $descending
+ * @return static
+ */
+ public function sortKeys($options = SORT_REGULAR, $descending = false)
+ {
+ return $this->passthru('sortKeys', func_get_args());
+ }
+
+ /**
+ * Sort the collection keys in descending order.
+ *
+ * @param int $options
+ * @return static
+ */
+ public function sortKeysDesc($options = SORT_REGULAR)
+ {
+ return $this->passthru('sortKeysDesc', func_get_args());
+ }
+
+ /**
+ * Sort the collection keys using a callback.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function sortKeysUsing(callable $callback)
+ {
+ return $this->passthru('sortKeysUsing', func_get_args());
+ }
+
+ /**
+ * Take the first or last {$limit} items.
+ *
+ * @param int $limit
+ * @return static
+ */
+ public function take($limit)
+ {
+ if ($limit < 0) {
+ return $this->passthru('take', func_get_args());
+ }
+
+ return new static(function () use ($limit) {
+ $iterator = $this->getIterator();
+
+ while ($limit--) {
+ if (! $iterator->valid()) {
+ break;
+ }
+
+ yield $iterator->key() => $iterator->current();
+
+ if ($limit) {
+ $iterator->next();
+ }
+ }
+ });
+ }
+
+ /**
+ * Take items in the collection until the given condition is met.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function takeUntil($value)
+ {
+ $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
+
+ return new static(function () use ($callback) {
+ foreach ($this as $key => $item) {
+ if ($callback($item, $key)) {
+ break;
+ }
+
+ yield $key => $item;
+ }
+ });
+ }
+
+ /**
+ * Take items in the collection until a given point in time.
+ *
+ * @param \DateTimeInterface $timeout
+ * @return static
+ */
+ public function takeUntilTimeout(DateTimeInterface $timeout)
+ {
+ $timeout = $timeout->getTimestamp();
+
+ return $this->takeWhile(function () use ($timeout) {
+ return $this->now() < $timeout;
+ });
+ }
+
+ /**
+ * Take items in the collection while the given condition is met.
+ *
+ * @param mixed $value
+ * @return static
+ */
+ public function takeWhile($value)
+ {
+ $callback = $this->useAsCallable($value) ? $value : $this->equality($value);
+
+ return $this->takeUntil(function ($item, $key) use ($callback) {
+ return ! $callback($item, $key);
+ });
+ }
+
+ /**
+ * Pass each item in the collection to the given callback, lazily.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function tapEach(callable $callback)
+ {
+ return new static(function () use ($callback) {
+ foreach ($this as $key => $value) {
+ $callback($value, $key);
+
+ yield $key => $value;
+ }
+ });
+ }
+
+ /**
+ * Convert a flatten "dot" notation array into an expanded array.
+ *
+ * @return static
+ */
+ public function undot()
+ {
+ return $this->passthru('undot', []);
+ }
+
+ /**
+ * Return only unique items from the collection array.
+ *
+ * @param string|callable|null $key
+ * @param bool $strict
+ * @return static
+ */
+ public function unique($key = null, $strict = false)
+ {
+ $callback = $this->valueRetriever($key);
+
+ return new static(function () use ($callback, $strict) {
+ $exists = [];
+
+ foreach ($this as $key => $item) {
+ if (! in_array($id = $callback($item, $key), $exists, $strict)) {
+ yield $key => $item;
+
+ $exists[] = $id;
+ }
+ }
+ });
+ }
+
+ /**
+ * Reset the keys on the underlying array.
+ *
+ * @return static
+ */
+ public function values()
+ {
+ return new static(function () {
+ foreach ($this as $item) {
+ yield $item;
+ }
+ });
+ }
+
+ /**
+ * Zip the collection together with one or more arrays.
+ *
+ * e.g. new LazyCollection([1, 2, 3])->zip([4, 5, 6]);
+ * => [[1, 4], [2, 5], [3, 6]]
+ *
+ * @param mixed ...$items
+ * @return static
+ */
+ public function zip($items)
+ {
+ $iterables = func_get_args();
+
+ return new static(function () use ($iterables) {
+ $iterators = Collection::make($iterables)->map(function ($iterable) {
+ return $this->makeIterator($iterable);
+ })->prepend($this->getIterator());
+
+ while ($iterators->contains->valid()) {
+ yield new static($iterators->map->current());
+
+ $iterators->each->next();
+ }
+ });
+ }
+
+ /**
+ * Pad collection to the specified length with a value.
+ *
+ * @param int $size
+ * @param mixed $value
+ * @return static
+ */
+ public function pad($size, $value)
+ {
+ if ($size < 0) {
+ return $this->passthru('pad', func_get_args());
+ }
+
+ return new static(function () use ($size, $value) {
+ $yielded = 0;
+
+ foreach ($this as $index => $item) {
+ yield $index => $item;
+
+ $yielded++;
+ }
+
+ while ($yielded++ < $size) {
+ yield $value;
+ }
+ });
+ }
+
+ /**
+ * Get the values iterator.
+ *
+ * @return \Traversable
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ return $this->makeIterator($this->source);
+ }
+
+ /**
+ * Count the number of items in the collection.
+ *
+ * @return int
+ */
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ if (is_array($this->source)) {
+ return count($this->source);
+ }
+
+ return iterator_count($this->getIterator());
+ }
+
+ /**
+ * Make an iterator from the given source.
+ *
+ * @param mixed $source
+ * @return \Traversable
+ */
+ protected function makeIterator($source)
+ {
+ if ($source instanceof IteratorAggregate) {
+ return $source->getIterator();
+ }
+
+ if (is_array($source)) {
+ return new ArrayIterator($source);
+ }
+
+ return $source();
+ }
+
+ /**
+ * Explode the "value" and "key" arguments passed to "pluck".
+ *
+ * @param string|array $value
+ * @param string|array|null $key
+ * @return array
+ */
+ protected function explodePluckParameters($value, $key)
+ {
+ $value = is_string($value) ? explode('.', $value) : $value;
+
+ $key = is_null($key) || is_array($key) ? $key : explode('.', $key);
+
+ return [$value, $key];
+ }
+
+ /**
+ * Pass this lazy collection through a method on the collection class.
+ *
+ * @param string $method
+ * @param array $params
+ * @return static
+ */
+ protected function passthru($method, array $params)
+ {
+ return new static(function () use ($method, $params) {
+ yield from $this->collect()->$method(...$params);
+ });
+ }
+
+ /**
+ * Get the current time.
+ *
+ * @return int
+ */
+ protected function now()
+ {
+ return time();
+ }
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php b/vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php
new file mode 100644
index 0000000..2f1187f
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/Traits/EnumeratesValues.php
@@ -0,0 +1,1116 @@
+all() : $value;
+ }
+
+ /**
+ * Create a new instance with no items.
+ *
+ * @return static
+ */
+ public static function empty()
+ {
+ return new static([]);
+ }
+
+ /**
+ * Create a new collection by invoking the callback a given amount of times.
+ *
+ * @param int $number
+ * @param callable|null $callback
+ * @return static
+ */
+ public static function times($number, callable $callback = null)
+ {
+ if ($number < 1) {
+ return new static;
+ }
+
+ return static::range(1, $number)
+ ->when($callback)
+ ->map($callback);
+ }
+
+ /**
+ * Alias for the "avg" method.
+ *
+ * @param callable|string|null $callback
+ * @return mixed
+ */
+ public function average($callback = null)
+ {
+ return $this->avg($callback);
+ }
+
+ /**
+ * Alias for the "contains" method.
+ *
+ * @param mixed $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return bool
+ */
+ public function some($key, $operator = null, $value = null)
+ {
+ return $this->contains(...func_get_args());
+ }
+
+ /**
+ * Determine if an item exists, using strict comparison.
+ *
+ * @param mixed $key
+ * @param mixed $value
+ * @return bool
+ */
+ public function containsStrict($key, $value = null)
+ {
+ if (func_num_args() === 2) {
+ return $this->contains(function ($item) use ($key, $value) {
+ return data_get($item, $key) === $value;
+ });
+ }
+
+ if ($this->useAsCallable($key)) {
+ return ! is_null($this->first($key));
+ }
+
+ foreach ($this as $item) {
+ if ($item === $key) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Dump the items and end the script.
+ *
+ * @param mixed ...$args
+ * @return void
+ */
+ public function dd(...$args)
+ {
+ $this->dump(...$args);
+
+ exit(1);
+ }
+
+ /**
+ * Dump the items.
+ *
+ * @return $this
+ */
+ public function dump()
+ {
+ (new Collection(func_get_args()))
+ ->push($this->all())
+ ->each(function ($item) {
+ VarDumper::dump($item);
+ });
+
+ return $this;
+ }
+
+ /**
+ * Execute a callback over each item.
+ *
+ * @param callable $callback
+ * @return $this
+ */
+ public function each(callable $callback)
+ {
+ foreach ($this as $key => $item) {
+ if ($callback($item, $key) === false) {
+ break;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Execute a callback over each nested chunk of items.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function eachSpread(callable $callback)
+ {
+ return $this->each(function ($chunk, $key) use ($callback) {
+ $chunk[] = $key;
+
+ return $callback(...$chunk);
+ });
+ }
+
+ /**
+ * Determine if all items pass the given truth test.
+ *
+ * @param string|callable $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return bool
+ */
+ public function every($key, $operator = null, $value = null)
+ {
+ if (func_num_args() === 1) {
+ $callback = $this->valueRetriever($key);
+
+ foreach ($this as $k => $v) {
+ if (! $callback($v, $k)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return $this->every($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Get the first item by the given key value pair.
+ *
+ * @param string $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return mixed
+ */
+ public function firstWhere($key, $operator = null, $value = null)
+ {
+ return $this->first($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Determine if the collection is not empty.
+ *
+ * @return bool
+ */
+ public function isNotEmpty()
+ {
+ return ! $this->isEmpty();
+ }
+
+ /**
+ * Run a map over each nested chunk of items.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function mapSpread(callable $callback)
+ {
+ return $this->map(function ($chunk, $key) use ($callback) {
+ $chunk[] = $key;
+
+ return $callback(...$chunk);
+ });
+ }
+
+ /**
+ * Run a grouping map over the items.
+ *
+ * The callback should return an associative array with a single key/value pair.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function mapToGroups(callable $callback)
+ {
+ $groups = $this->mapToDictionary($callback);
+
+ return $groups->map([$this, 'make']);
+ }
+
+ /**
+ * Map a collection and flatten the result by a single level.
+ *
+ * @param callable $callback
+ * @return static
+ */
+ public function flatMap(callable $callback)
+ {
+ return $this->map($callback)->collapse();
+ }
+
+ /**
+ * Map the values into a new class.
+ *
+ * @param string $class
+ * @return static
+ */
+ public function mapInto($class)
+ {
+ return $this->map(function ($value, $key) use ($class) {
+ return new $class($value, $key);
+ });
+ }
+
+ /**
+ * Get the min value of a given key.
+ *
+ * @param callable|string|null $callback
+ * @return mixed
+ */
+ public function min($callback = null)
+ {
+ $callback = $this->valueRetriever($callback);
+
+ return $this->map(function ($value) use ($callback) {
+ return $callback($value);
+ })->filter(function ($value) {
+ return ! is_null($value);
+ })->reduce(function ($result, $value) {
+ return is_null($result) || $value < $result ? $value : $result;
+ });
+ }
+
+ /**
+ * Get the max value of a given key.
+ *
+ * @param callable|string|null $callback
+ * @return mixed
+ */
+ public function max($callback = null)
+ {
+ $callback = $this->valueRetriever($callback);
+
+ return $this->filter(function ($value) {
+ return ! is_null($value);
+ })->reduce(function ($result, $item) use ($callback) {
+ $value = $callback($item);
+
+ return is_null($result) || $value > $result ? $value : $result;
+ });
+ }
+
+ /**
+ * "Paginate" the collection by slicing it into a smaller collection.
+ *
+ * @param int $page
+ * @param int $perPage
+ * @return static
+ */
+ public function forPage($page, $perPage)
+ {
+ $offset = max(0, ($page - 1) * $perPage);
+
+ return $this->slice($offset, $perPage);
+ }
+
+ /**
+ * Partition the collection into two arrays using the given callback or key.
+ *
+ * @param callable|string $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return static
+ */
+ public function partition($key, $operator = null, $value = null)
+ {
+ $passed = [];
+ $failed = [];
+
+ $callback = func_num_args() === 1
+ ? $this->valueRetriever($key)
+ : $this->operatorForWhere(...func_get_args());
+
+ foreach ($this as $key => $item) {
+ if ($callback($item, $key)) {
+ $passed[$key] = $item;
+ } else {
+ $failed[$key] = $item;
+ }
+ }
+
+ return new static([new static($passed), new static($failed)]);
+ }
+
+ /**
+ * Get the sum of the given values.
+ *
+ * @param callable|string|null $callback
+ * @return mixed
+ */
+ public function sum($callback = null)
+ {
+ $callback = is_null($callback)
+ ? $this->identity()
+ : $this->valueRetriever($callback);
+
+ return $this->reduce(function ($result, $item) use ($callback) {
+ return $result + $callback($item);
+ }, 0);
+ }
+
+ /**
+ * Apply the callback if the value is truthy.
+ *
+ * @param bool|mixed $value
+ * @param callable|null $callback
+ * @param callable|null $default
+ * @return static|mixed
+ */
+ public function when($value, callable $callback = null, callable $default = null)
+ {
+ if (! $callback) {
+ return new HigherOrderWhenProxy($this, $value);
+ }
+
+ if ($value) {
+ return $callback($this, $value);
+ } elseif ($default) {
+ return $default($this, $value);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Apply the callback if the collection is empty.
+ *
+ * @param callable $callback
+ * @param callable|null $default
+ * @return static|mixed
+ */
+ public function whenEmpty(callable $callback, callable $default = null)
+ {
+ return $this->when($this->isEmpty(), $callback, $default);
+ }
+
+ /**
+ * Apply the callback if the collection is not empty.
+ *
+ * @param callable $callback
+ * @param callable|null $default
+ * @return static|mixed
+ */
+ public function whenNotEmpty(callable $callback, callable $default = null)
+ {
+ return $this->when($this->isNotEmpty(), $callback, $default);
+ }
+
+ /**
+ * Apply the callback if the value is falsy.
+ *
+ * @param bool $value
+ * @param callable $callback
+ * @param callable|null $default
+ * @return static|mixed
+ */
+ public function unless($value, callable $callback, callable $default = null)
+ {
+ return $this->when(! $value, $callback, $default);
+ }
+
+ /**
+ * Apply the callback unless the collection is empty.
+ *
+ * @param callable $callback
+ * @param callable|null $default
+ * @return static|mixed
+ */
+ public function unlessEmpty(callable $callback, callable $default = null)
+ {
+ return $this->whenNotEmpty($callback, $default);
+ }
+
+ /**
+ * Apply the callback unless the collection is not empty.
+ *
+ * @param callable $callback
+ * @param callable|null $default
+ * @return static|mixed
+ */
+ public function unlessNotEmpty(callable $callback, callable $default = null)
+ {
+ return $this->whenEmpty($callback, $default);
+ }
+
+ /**
+ * Filter items by the given key value pair.
+ *
+ * @param string $key
+ * @param mixed $operator
+ * @param mixed $value
+ * @return static
+ */
+ public function where($key, $operator = null, $value = null)
+ {
+ return $this->filter($this->operatorForWhere(...func_get_args()));
+ }
+
+ /**
+ * Filter items where the value for the given key is null.
+ *
+ * @param string|null $key
+ * @return static
+ */
+ public function whereNull($key = null)
+ {
+ return $this->whereStrict($key, null);
+ }
+
+ /**
+ * Filter items where the value for the given key is not null.
+ *
+ * @param string|null $key
+ * @return static
+ */
+ public function whereNotNull($key = null)
+ {
+ return $this->where($key, '!==', null);
+ }
+
+ /**
+ * Filter items by the given key value pair using strict comparison.
+ *
+ * @param string $key
+ * @param mixed $value
+ * @return static
+ */
+ public function whereStrict($key, $value)
+ {
+ return $this->where($key, '===', $value);
+ }
+
+ /**
+ * Filter items by the given key value pair.
+ *
+ * @param string $key
+ * @param mixed $values
+ * @param bool $strict
+ * @return static
+ */
+ public function whereIn($key, $values, $strict = false)
+ {
+ $values = $this->getArrayableItems($values);
+
+ return $this->filter(function ($item) use ($key, $values, $strict) {
+ return in_array(data_get($item, $key), $values, $strict);
+ });
+ }
+
+ /**
+ * Filter items by the given key value pair using strict comparison.
+ *
+ * @param string $key
+ * @param mixed $values
+ * @return static
+ */
+ public function whereInStrict($key, $values)
+ {
+ return $this->whereIn($key, $values, true);
+ }
+
+ /**
+ * Filter items such that the value of the given key is between the given values.
+ *
+ * @param string $key
+ * @param array $values
+ * @return static
+ */
+ public function whereBetween($key, $values)
+ {
+ return $this->where($key, '>=', reset($values))->where($key, '<=', end($values));
+ }
+
+ /**
+ * Filter items such that the value of the given key is not between the given values.
+ *
+ * @param string $key
+ * @param array $values
+ * @return static
+ */
+ public function whereNotBetween($key, $values)
+ {
+ return $this->filter(function ($item) use ($key, $values) {
+ return data_get($item, $key) < reset($values) || data_get($item, $key) > end($values);
+ });
+ }
+
+ /**
+ * Filter items by the given key value pair.
+ *
+ * @param string $key
+ * @param mixed $values
+ * @param bool $strict
+ * @return static
+ */
+ public function whereNotIn($key, $values, $strict = false)
+ {
+ $values = $this->getArrayableItems($values);
+
+ return $this->reject(function ($item) use ($key, $values, $strict) {
+ return in_array(data_get($item, $key), $values, $strict);
+ });
+ }
+
+ /**
+ * Filter items by the given key value pair using strict comparison.
+ *
+ * @param string $key
+ * @param mixed $values
+ * @return static
+ */
+ public function whereNotInStrict($key, $values)
+ {
+ return $this->whereNotIn($key, $values, true);
+ }
+
+ /**
+ * Filter the items, removing any items that don't match the given type(s).
+ *
+ * @param string|string[] $type
+ * @return static
+ */
+ public function whereInstanceOf($type)
+ {
+ return $this->filter(function ($value) use ($type) {
+ if (is_array($type)) {
+ foreach ($type as $classType) {
+ if ($value instanceof $classType) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return $value instanceof $type;
+ });
+ }
+
+ /**
+ * Pass the collection to the given callback and return the result.
+ *
+ * @param callable $callback
+ * @return mixed
+ */
+ public function pipe(callable $callback)
+ {
+ return $callback($this);
+ }
+
+ /**
+ * Pass the collection into a new class.
+ *
+ * @param string $class
+ * @return mixed
+ */
+ public function pipeInto($class)
+ {
+ return new $class($this);
+ }
+
+ /**
+ * Pass the collection through a series of callable pipes and return the result.
+ *
+ * @param array
$pipes
+ * @return mixed
+ */
+ public function pipeThrough($pipes)
+ {
+ return static::make($pipes)->reduce(
+ function ($carry, $pipe) {
+ return $pipe($carry);
+ },
+ $this,
+ );
+ }
+
+ /**
+ * Pass the collection to the given callback and then return it.
+ *
+ * @param callable $callback
+ * @return $this
+ */
+ public function tap(callable $callback)
+ {
+ $callback(clone $this);
+
+ return $this;
+ }
+
+ /**
+ * Reduce the collection to a single value.
+ *
+ * @param callable $callback
+ * @param mixed $initial
+ * @return mixed
+ */
+ public function reduce(callable $callback, $initial = null)
+ {
+ $result = $initial;
+
+ foreach ($this as $key => $value) {
+ $result = $callback($result, $value, $key);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Reduce the collection to multiple aggregate values.
+ *
+ * @param callable $callback
+ * @param mixed ...$initial
+ * @return array
+ *
+ * @deprecated Use "reduceSpread" instead
+ *
+ * @throws \UnexpectedValueException
+ */
+ public function reduceMany(callable $callback, ...$initial)
+ {
+ return $this->reduceSpread($callback, ...$initial);
+ }
+
+ /**
+ * Reduce the collection to multiple aggregate values.
+ *
+ * @param callable $callback
+ * @param mixed ...$initial
+ * @return array
+ *
+ * @throws \UnexpectedValueException
+ */
+ public function reduceSpread(callable $callback, ...$initial)
+ {
+ $result = $initial;
+
+ foreach ($this as $key => $value) {
+ $result = call_user_func_array($callback, array_merge($result, [$value, $key]));
+
+ if (! is_array($result)) {
+ throw new UnexpectedValueException(sprintf(
+ "%s::reduceMany expects reducer to return an array, but got a '%s' instead.",
+ class_basename(static::class), gettype($result)
+ ));
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Reduce an associative collection to a single value.
+ *
+ * @param callable $callback
+ * @param mixed $initial
+ * @return mixed
+ */
+ public function reduceWithKeys(callable $callback, $initial = null)
+ {
+ return $this->reduce($callback, $initial);
+ }
+
+ /**
+ * Create a collection of all elements that do not pass a given truth test.
+ *
+ * @param callable|mixed $callback
+ * @return static
+ */
+ public function reject($callback = true)
+ {
+ $useAsCallable = $this->useAsCallable($callback);
+
+ return $this->filter(function ($value, $key) use ($callback, $useAsCallable) {
+ return $useAsCallable
+ ? ! $callback($value, $key)
+ : $value != $callback;
+ });
+ }
+
+ /**
+ * Return only unique items from the collection array using strict comparison.
+ *
+ * @param string|callable|null $key
+ * @return static
+ */
+ public function uniqueStrict($key = null)
+ {
+ return $this->unique($key, true);
+ }
+
+ /**
+ * Collect the values into a collection.
+ *
+ * @return \Tightenco\Collect\Support\Collection
+ */
+ public function collect()
+ {
+ return new Collection($this->all());
+ }
+
+ /**
+ * Get the collection of items as a plain array.
+ *
+ * @return array
+ */
+ public function toArray()
+ {
+ return $this->map(function ($value) {
+ return $value instanceof Arrayable ? $value->toArray() : $value;
+ })->all();
+ }
+
+ /**
+ * Convert the object into something JSON serializable.
+ *
+ * @return array
+ */
+ #[\ReturnTypeWillChange]
+ public function jsonSerialize()
+ {
+ return array_map(function ($value) {
+ if ($value instanceof JsonSerializable) {
+ return $value->jsonSerialize();
+ } elseif ($value instanceof Jsonable) {
+ return json_decode($value->toJson(), true);
+ } elseif ($value instanceof Arrayable) {
+ return $value->toArray();
+ }
+
+ return $value;
+ }, $this->all());
+ }
+
+ /**
+ * Get the collection of items as JSON.
+ *
+ * @param int $options
+ * @return string
+ */
+ public function toJson($options = 0)
+ {
+ return json_encode($this->jsonSerialize(), $options);
+ }
+
+ /**
+ * Get a CachingIterator instance.
+ *
+ * @param int $flags
+ * @return \CachingIterator
+ */
+ public function getCachingIterator($flags = CachingIterator::CALL_TOSTRING)
+ {
+ return new CachingIterator($this->getIterator(), $flags);
+ }
+
+ /**
+ * Convert the collection to its string representation.
+ *
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->escapeWhenCastingToString
+ ? e($this->toJson())
+ : $this->toJson();
+ }
+
+ /**
+ * Indicate that the model's string representation should be escaped when __toString is invoked.
+ *
+ * @param bool $escape
+ * @return $this
+ */
+ public function escapeWhenCastingToString($escape = true)
+ {
+ $this->escapeWhenCastingToString = $escape;
+
+ return $this;
+ }
+
+ /**
+ * Add a method to the list of proxied methods.
+ *
+ * @param string $method
+ * @return void
+ */
+ public static function proxy($method)
+ {
+ static::$proxies[] = $method;
+ }
+
+ /**
+ * Dynamically access collection proxies.
+ *
+ * @param string $key
+ * @return mixed
+ *
+ * @throws \Exception
+ */
+ public function __get($key)
+ {
+ if (! in_array($key, static::$proxies)) {
+ throw new Exception("Property [{$key}] does not exist on this collection instance.");
+ }
+
+ return new HigherOrderCollectionProxy($this, $key);
+ }
+
+ /**
+ * Results array of items from Collection or Arrayable.
+ *
+ * @param mixed $items
+ * @return array
+ */
+ protected function getArrayableItems($items)
+ {
+ if (is_array($items)) {
+ return $items;
+ } elseif ($items instanceof Enumerable) {
+ return $items->all();
+ } elseif ($items instanceof Arrayable) {
+ return $items->toArray();
+ } elseif ($items instanceof Jsonable) {
+ return json_decode($items->toJson(), true);
+ } elseif ($items instanceof JsonSerializable) {
+ return (array) $items->jsonSerialize();
+ } elseif ($items instanceof Traversable) {
+ return iterator_to_array($items);
+ } elseif ($items instanceof UnitEnum) {
+ return [$items];
+ }
+
+ return (array) $items;
+ }
+
+ /**
+ * Get an operator checker callback.
+ *
+ * @param string $key
+ * @param string|null $operator
+ * @param mixed $value
+ * @return \Closure
+ */
+ protected function operatorForWhere($key, $operator = null, $value = null)
+ {
+ if (func_num_args() === 1) {
+ $value = true;
+
+ $operator = '=';
+ }
+
+ if (func_num_args() === 2) {
+ $value = $operator;
+
+ $operator = '=';
+ }
+
+ return function ($item) use ($key, $operator, $value) {
+ $retrieved = data_get($item, $key);
+
+ $strings = array_filter([$retrieved, $value], function ($value) {
+ return is_string($value) || (is_object($value) && method_exists($value, '__toString'));
+ });
+
+ if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) {
+ return in_array($operator, ['!=', '<>', '!==']);
+ }
+
+ switch ($operator) {
+ default:
+ case '=':
+ case '==': return $retrieved == $value;
+ case '!=':
+ case '<>': return $retrieved != $value;
+ case '<': return $retrieved < $value;
+ case '>': return $retrieved > $value;
+ case '<=': return $retrieved <= $value;
+ case '>=': return $retrieved >= $value;
+ case '===': return $retrieved === $value;
+ case '!==': return $retrieved !== $value;
+ }
+ };
+ }
+
+ /**
+ * Determine if the given value is callable, but not a string.
+ *
+ * @param mixed $value
+ * @return bool
+ */
+ protected function useAsCallable($value)
+ {
+ return ! is_string($value) && is_callable($value);
+ }
+
+ /**
+ * Get a value retrieving callback.
+ *
+ * @param callable|string|null $value
+ * @return callable
+ */
+ protected function valueRetriever($value)
+ {
+ if ($this->useAsCallable($value)) {
+ return $value;
+ }
+
+ return function ($item) use ($value) {
+ return data_get($item, $value);
+ };
+ }
+
+ /**
+ * Make a function to check an item's equality.
+ *
+ * @param mixed $value
+ * @return \Closure
+ */
+ protected function equality($value)
+ {
+ return function ($item) use ($value) {
+ return $item === $value;
+ };
+ }
+
+ /**
+ * Make a function using another function, by negating its result.
+ *
+ * @param \Closure $callback
+ * @return \Closure
+ */
+ protected function negate(Closure $callback)
+ {
+ return function (...$params) use ($callback) {
+ return ! $callback(...$params);
+ };
+ }
+
+ /**
+ * Make a function that returns what's passed to it.
+ *
+ * @return \Closure
+ */
+ protected function identity()
+ {
+ return function ($value) {
+ return $value;
+ };
+ }
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/Traits/Macroable.php b/vendor/tightenco/collect/src/Collect/Support/Traits/Macroable.php
new file mode 100644
index 0000000..7e9fbd5
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/Traits/Macroable.php
@@ -0,0 +1,126 @@
+getMethods(
+ ReflectionMethod::IS_PUBLIC | ReflectionMethod::IS_PROTECTED
+ );
+
+ foreach ($methods as $method) {
+ if ($replace || ! static::hasMacro($method->name)) {
+ $method->setAccessible(true);
+ static::macro($method->name, $method->invoke($mixin));
+ }
+ }
+ }
+
+ /**
+ * Checks if macro is registered.
+ *
+ * @param string $name
+ * @return bool
+ */
+ public static function hasMacro($name)
+ {
+ return isset(static::$macros[$name]);
+ }
+
+ /**
+ * Flush the existing macros.
+ *
+ * @return void
+ */
+ public static function flushMacros()
+ {
+ static::$macros = [];
+ }
+
+ /**
+ * Dynamically handle calls to the class.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ *
+ * @throws \BadMethodCallException
+ */
+ public static function __callStatic($method, $parameters)
+ {
+ if (! static::hasMacro($method)) {
+ throw new BadMethodCallException(sprintf(
+ 'Method %s::%s does not exist.', static::class, $method
+ ));
+ }
+
+ $macro = static::$macros[$method];
+
+ if ($macro instanceof Closure) {
+ $macro = $macro->bindTo(null, static::class);
+ }
+
+ return $macro(...$parameters);
+ }
+
+ /**
+ * Dynamically handle calls to the class.
+ *
+ * @param string $method
+ * @param array $parameters
+ * @return mixed
+ *
+ * @throws \BadMethodCallException
+ */
+ public function __call($method, $parameters)
+ {
+ if (! static::hasMacro($method)) {
+ throw new BadMethodCallException(sprintf(
+ 'Method %s::%s does not exist.', static::class, $method
+ ));
+ }
+
+ $macro = static::$macros[$method];
+
+ if ($macro instanceof Closure) {
+ $macro = $macro->bindTo($this, static::class);
+ }
+
+ return $macro(...$parameters);
+ }
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php b/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php
new file mode 100644
index 0000000..9d75d26
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/Traits/Tappable.php
@@ -0,0 +1,17 @@
+ Illuminate\Contracts\Support\Arrayable::class,
+ Tightenco\Collect\Contracts\Support\Jsonable::class => Illuminate\Contracts\Support\Jsonable::class,
+ Tightenco\Collect\Contracts\Support\Htmlable::class => Illuminate\Contracts\Support\Htmlable::class,
+ Tightenco\Collect\Contracts\Support\CanBeEscapedWhenCastToString::class => Illuminate\Contracts\Support\CanBeEscapedWhenCastToString::class,
+ Tightenco\Collect\Support\Arr::class => Illuminate\Support\Arr::class,
+ Tightenco\Collect\Support\Collection::class => Illuminate\Support\Collection::class,
+ Tightenco\Collect\Support\Enumerable::class => Illuminate\Support\Enumerable::class,
+ Tightenco\Collect\Support\HigherOrderCollectionProxy::class => Illuminate\Support\HigherOrderCollectionProxy::class,
+ Tightenco\Collect\Support\HigherOrderWhenProxy::class => Illuminate\Support\HigherOrderWhenProxy::class,
+ Tightenco\Collect\Support\LazyCollection::class => Illuminate\Support\LazyCollection::class,
+ Tightenco\Collect\Support\Traits\EnumeratesValues::class => Illuminate\Support\Traits\EnumeratesValues::class,
+];
+
+# echo "\n\n-- Aliasing....\n---------------------------------------------\n\n";
+
+foreach ($aliases as $tighten => $illuminate) {
+ if (! class_exists($illuminate) && ! interface_exists($illuminate) && ! trait_exists($illuminate)) {
+ # echo "Aliasing {$tighten} to {$illuminate}.\n";
+ class_alias($tighten, $illuminate);
+ }
+}
diff --git a/vendor/tightenco/collect/src/Collect/Support/helpers.php b/vendor/tightenco/collect/src/Collect/Support/helpers.php
new file mode 100644
index 0000000..886a141
--- /dev/null
+++ b/vendor/tightenco/collect/src/Collect/Support/helpers.php
@@ -0,0 +1,122 @@
+ $segment) {
+ unset($key[$i]);
+
+ if (is_null($segment)) {
+ return $target;
+ }
+
+ if ($segment === '*') {
+ if ($target instanceof Collection) {
+ $target = $target->all();
+ } elseif (! is_array($target)) {
+ return value($default);
+ }
+
+ $result = [];
+
+ foreach ($target as $item) {
+ $result[] = data_get($item, $key);
+ }
+
+ return in_array('*', $key) ? Arr::collapse($result) : $result;
+ }
+
+ if (Arr::accessible($target) && Arr::exists($target, $segment)) {
+ $target = $target[$segment];
+ } elseif (is_object($target) && isset($target->{$segment})) {
+ $target = $target->{$segment};
+ } else {
+ return value($default);
+ }
+ }
+
+ return $target;
+ }
+ }
+
+ if (! function_exists('tap')) {
+ /**
+ * Call the given Closure with the given value then return the value.
+ *
+ * @param mixed $value
+ * @param callable|null $callback
+ * @return mixed
+ */
+ function tap($value, $callback = null)
+ {
+ if (is_null($callback)) {
+ return new HigherOrderTapProxy($value);
+ }
+
+ $callback($value);
+
+ return $value;
+ }
+ }
+
+ if (! function_exists('class_basename')) {
+ /**
+ * Get the class "basename" of the given object / class.
+ *
+ * @param string|object $class
+ * @return string
+ */
+ function class_basename($class)
+ {
+ $class = is_object($class) ? get_class($class) : $class;
+
+ return basename(str_replace('\\', '/', $class));
+ }
+ }
+}
diff --git a/view/taoler/index/article/add.html b/view/taoler/index/article/add.html
index 6edaae6..eb5a76b 100644
--- a/view/taoler/index/article/add.html
+++ b/view/taoler/index/article/add.html
@@ -252,8 +252,9 @@
});
// 获取百度分词接口的关键词
- function getBdiduWords(flag,title,content) {
- console.log(flag,title,content)
+ function getBdiduWords(flag,title,content)
+ {
+ // console.log(flag,title,content)
$.post("{:url('article/keywords')}",{ keywords: title, content:content, flag: flag }, function (res) {
if (res.code == 0) {
$("input[name='keywords']").val(res.data.join(','));
diff --git a/view/taoler/index/article/ask/detail.html b/view/taoler/index/article/ask/detail.html
index a651997..1c221bf 100644
--- a/view/taoler/index/article/ask/detail.html
+++ b/view/taoler/index/article/ask/detail.html
@@ -110,7 +110,7 @@
{//评论区}
- {if ( config('taoler.config.is_reply') == 1 ) AND ( $article.is_reply == 1 )}
+ {if session('?user_id') AND ( config('taoler.config.is_reply') == 1 ) AND ( $article.is_reply == 1 )}