diff --git a/app/BaseController.php b/app/BaseController.php index c18d659..357018d 100644 --- a/app/BaseController.php +++ b/app/BaseController.php @@ -5,6 +5,7 @@ namespace app; use think\App; use think\exception\ValidateException; +use think\response\Json; use think\Validate; use think\Response; use think\exception\HttpResponseException; @@ -410,7 +411,7 @@ abstract class BaseController /** * 标题调用百度关键词词条 * - * @return void + * @return Json */ public function getBdiduSearchWordList($words) { diff --git a/app/Request.php b/app/Request.php index 6173cee..f3367ee 100644 --- a/app/Request.php +++ b/app/Request.php @@ -18,6 +18,6 @@ class Request extends \think\Request //protected $filter = ['trim','htmlspecialchars']; //protected $filter = ['trim','strip_tags']; //protected $filter = ['htmlspecialchars']; - //protected $filter = ['trim']; + protected $filter = ['trim']; } diff --git a/app/admin/controller/Addons.php b/app/admin/controller/Addons.php index f79e009..6e0c952 100644 --- a/app/admin/controller/Addons.php +++ b/app/admin/controller/Addons.php @@ -2,14 +2,16 @@ namespace app\admin\controller; use app\common\controller\AdminController; +use app\common\lib\SqlFile; use think\facade\View; -use think\facade\Db; use think\facade\Request; use think\facade\Config; use app\admin\model\Addons as AddonsModel; use taoler\com\Files; use taoler\com\Api; use app\common\lib\Zip; +use think\response\Json; +use app\admin\model\AuthRule; class Addons extends AdminController { @@ -20,8 +22,12 @@ class Addons extends AdminController { return View::fetch(); } - - public function addonsList() + + /** + * 插件列表 + * @return Json + */ + public function addonsList() :Json { $type = input('type'); @@ -59,24 +65,21 @@ class Addons extends AdminController //在线 case 'onlineAddons': $url = $this->getSystem()['api_url'].'/v1/addons'; - $addons = Api::urlGet($url,[]); + $addons = Api::urlGet($url); if( $addons->code !== -1){ $res['code'] = 0; $res['msg'] = ''; $res['data'] = $addons->data; $res['col'] = [ ['type' => 'numbers'], - ['field' => 'name','title'=> '插件', 'width'=> 150], - ['field'=> 'title','title'=> '标题', 'width'=> 100], - ['field'=> 'version','title'=> '版本', 'width'=> 100], + ['field' => 'title','title'=> '插件', 'width'=> 200], + ['field' => 'description','title'=> '简介', 'minWidth'=> 200], ['field' => 'author','title'=> '作者', 'width'=> 100], - ['field' => 'description','title'=> '简介', 'minWidth'=> 200], - ['field' => 'price','title'=> '价格(元)'], - ['field' => 'status','title'=> '状态', 'width'=> 100], - ['field' => 'install','title'=> '安装', 'width'=> 100], - ['field' => 'vers','title'=> '版本选择','exportTemplet' => "function(d, obj){console.log(obj) var td = obj.td(this.field); return td.find('select').val();}"], - ['field' => 'ctime','title'=> '时间', 'width'=> 150], - ['title' => '操作', 'width'=> 300, 'align'=>'center', 'toolbar'=> '#addons-tool'] + ['field' => 'price','title'=> '价格(元)','width'=> 80], + ['field' => 'downloads','title'=> '下载', 'width'=> 70], + ['field' => 'version','title'=> '版本', 'width'=> 70], + ['field' => 'status','title'=> '状态', 'width'=> 70], + ['title' => '操作', 'width'=> 180, 'align'=>'center', 'toolbar'=> '#addons-tool'] ]; } else { $res = ['code'=>-1,'msg'=>'未获取到服务器信息']; @@ -112,9 +115,11 @@ class Addons extends AdminController /** * 编辑版本 - * - * @param int $id - * @return \think\Response + * @param $id + * @return string|Json + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException */ public function edit($id) { @@ -135,11 +140,8 @@ class Addons extends AdminController } /** - * 上传版本的zip资源 - * - * @param - * @param int $id - * @return \think\Response + * 上传插件文件zip + * @return Json */ public function uploadZip() { @@ -148,88 +150,113 @@ class Addons extends AdminController try { validate(['file'=>'filesize:2048|fileExt:zip,rar,7z']) ->check(array($file)); - $savename = \think\facade\Filesystem::disk('public')->putFile('addons',$file); + $saveName = \think\facade\Filesystem::disk('public')->putFile('addons',$file); } catch (\think\exception\ValidateException $e) { - echo $e->getMessage(); + return json(['code' => -1,'msg' => $e->getMessage()]); } $upload = Config::get('filesystem.disks.public.url'); - if($savename){ - $name_path =str_replace('\\',"/",$upload.'/'.$savename); - $res = ['code'=>0,'msg'=>'插件上传成功','src'=>$name_path]; - } else { - $res = ['code'=>-1,'msg'=>'上传错误']; - } + if($saveName){ + $name_path =str_replace('\\',"/",$upload.'/'.$saveName); + $res = ['code'=>0,'msg'=>'插件上传成功','src'=>$name_path]; + } else { + $res = ['code'=>-1,'msg'=>'上传错误']; + } return json($res); } //安装插件 public function install() { - $data = Request::param(); + $data = Request::param(); + $url = $this->getSystem()['api_url'].'/v1/getaddons'; - $addons = Api::urlPost($url,['name'=>$data['name'],'version'=>$data['version']]); - if( $addons->code == -1) { - return json(['code'=>$addons->code,'msg'=>$addons->msg]); - } - //是否安装? - $addInstalledVersion = get_addons_info($data['name']); - if(!empty($addInstalledVersion)){ - $verRes = version_compare($data['version'],$addInstalledVersion['version'],'>'); - if(!$verRes){ - return json(['code'=>-1,'msg'=>'不能降级,请选择正确版本']); - } - //$tpl_ver_res = version_compare($addInstalledVersion['template_version'], config('taoler.template_version'),'<'); - } + $addons = Api::urlPost($url,['name'=>$data['name'],'version'=>$data['version']]); + if( $addons->code == -1) { + return json(['code'=>$addons->code,'msg'=>$addons->msg]); + } + //是否安装? + $addInstalledVersion = get_addons_info($data['name']); +// if(!empty($addInstalledVersion)){ +// $verRes = version_compare($data['version'],$addInstalledVersion['version'],'>'); +// if(!$verRes){ +// return json(['code'=>-1,'msg'=>'不能降级,请选择正确版本']); +// } +// //$tpl_ver_res = version_compare($addInstalledVersion['template_version'], config('taoler.template_version'),'<'); +// } - $file_url = $addons->addons_src; - //判断远程文件是否可用存在 - $header = get_headers($file_url, true); - if(!isset($header[0]) && (strpos($header[0], '200') || strpos($header[0], '304'))){ - return json(['code'=>-1,'msg'=>'获取远程文件失败']); + $file_url = $addons->addons_src; + //判断远程文件是否可用存在 + $header = get_headers($file_url, true); + if(!isset($header[0]) && (strpos($header[0], '200') || strpos($header[0], '304'))){ + return json(['code'=>-1,'msg'=>'获取远程文件失败']); + } + + //把远程文件放入本地 + + //拼接路径 + $addons_dir = Files::getDirPath('../runtime/addons/'); + Files::mkdirs($addons_dir); + + $package_file = $addons_dir . $data['name'] .'.zip'; //升级的压缩包文件 + $cpfile = copy($file_url,$package_file); + if(!$cpfile) return json(['code'=>-1,'msg'=>'下载升级文件失败']); + + $uzip = new Zip(); + $zipDir = strstr($package_file, '.zip',true); //返回文件名后缀前的字符串 + $zipPath = Files::getDirPath($zipDir); //转换为带/的路径 压缩文件解压到的路径 + $unzip_res = $uzip->unzip($package_file,$zipPath,true); + if(!$unzip_res) return json(['code'=>-1,'msg'=>'解压失败']); + + //升级插件 + + //升级前的写入文件权限检查 + $allUpdateFiles = Files::getAllFile($zipPath); + + if (empty($allUpdateFiles)) return json(['code' => -1, 'msg' => '无可更新文件。']); + $checkString = ''; + foreach ($allUpdateFiles as $updateFile) { + $coverFile = ltrim(str_replace($zipPath, '', $updateFile), DIRECTORY_SEPARATOR); + $dirPath = dirname('../'.$coverFile); + if (file_exists('../'.$coverFile)) { + if (!is_writable('../'.$coverFile)) $checkString .= $coverFile . ' [' . '无写入权限' . ']
'; + } else { + if (!is_dir($dirPath)) @mkdir($dirPath, 0777, true); + if (!is_writable($dirPath)) $checkString .= $dirPath . ' [' . '无写入权限' . ']
'; + } + } + + if (!empty($checkString)) return json(['code' => -1, 'msg' => $checkString]); + $addonsPath = '../'; + $cpRes = Files::copyDirs($zipPath,$addonsPath); + $cpData = $cpRes->getData(); + //更新失败 + if($cpData['code'] == -1) return json(['code'=>-1,'msg'=>$cpData['msg']]); + + //添加数据库 + $sqlInstallFile = root_path().'addons/'.$data['name'].'/install.sql'; + if(file_exists($sqlInstallFile)) { + SqlFile::dbExecute($sqlInstallFile); + } + //安装菜单 + $menuFile = root_path().'addons/'.$data['name'].'/menu.php'; + if(file_exists($menuFile)) { + include_once $menuFile; + $menu = Config::get('menu'); + + if(!empty($menu)){ + if(isset($menu['is_nav']) && $menu['is_nav']==1){ + $pid = 0; + }else{ + $pid = AuthRule::where('name','addons')->value('id'); + } + $menu_arr[] = $menu['menu']; + $this->addAddonMenu($menu_arr, (int)$pid,1); + } } - //把远程文件放入本地 - - //拼接路径 - $addons_dir = Files::getDirPath('../runtime/addons/'); - Files::mkdirs($addons_dir); - - $package_file = $addons_dir . $data['name'] .'.zip'; //升级的压缩包文件 - $cpfile = copy($file_url,$package_file); - if(!$cpfile) return json(['code'=>-1,'msg'=>'下载升级文件失败']); - - $uzip = new Zip(); - $zipDir = strstr($package_file, '.zip',true); //返回文件名后缀前的字符串 - $zipPath = Files::getDirPath($zipDir); //转换为带/的路径 压缩文件解压到的路径 - $unzip_res = $uzip->unzip($package_file,$zipPath,true); - if(!$unzip_res) return json(['code'=>-1,'msg'=>'解压失败']); - //升级插件 - - //升级前的写入文件权限检查 - $allUpdateFiles = Files::getAllFile($zipPath); - - if (empty($allUpdateFiles)) return json(['code' => -1, 'msg' => '无可更新文件。']); - $checkString = ''; - foreach ($allUpdateFiles as $updateFile) { - $coverFile = ltrim(str_replace($zipPath, '', $updateFile), DIRECTORY_SEPARATOR); - $dirPath = dirname('../'.$coverFile); - if (file_exists('../'.$coverFile)) { - if (!is_writable('../'.$coverFile)) $checkString .= $coverFile . ' [' . '无写入权限' . ']
'; - } else { - if (!is_dir($dirPath)) @mkdir($dirPath, 0777, true); - if (!is_writable($dirPath)) $checkString .= $dirPath . ' [' . '无写入权限' . ']
'; - } - } - - if (!empty($checkString)) return json(['code' => -1, 'msg' => $checkString]); - $addonsPath = '../'; - $cpRes = Files::copyDirs($zipPath,$addonsPath); - $cpData = $cpRes->getData(); - //更新失败 - if($cpData['code'] == -1) return json(['code'=>-1,'msg'=>$cpData['msg']]); Files::delDirAndFile('../runtime/addons/'); return json(['code'=>0,'msg'=>'插件安装成功!']); @@ -244,6 +271,23 @@ class Addons extends AdminController $addonsPath = '../addons/'.$name; $staticPath = 'addons/'.$name; + //卸载菜单 + $menuFile = root_path().'addons/'.$name.'/menu.php'; + if(file_exists($menuFile)) { + include_once $menuFile; + $menu = Config::get('menu'); + if(!empty($menu)){ + $menu_arr[] = $menu['menu']; + $this->delAddonMenu($menu_arr); + } + } + + //卸载插件数据库 + $sqlUninstallFile = root_path().'addons/'.$name.'/uninstall.sql'; + if(file_exists($sqlUninstallFile)) { + SqlFile::dbExecute($sqlUninstallFile); + } + if (is_dir($staticPath)) { Files::delDir($staticPath); } @@ -253,6 +297,7 @@ class Addons extends AdminController } else { return json(['code'=>-1,'msg'=>'卸载失败']); } + return json(['code'=>0,'msg'=>'卸载成功']); } //启用插件 @@ -317,4 +362,52 @@ class Addons extends AdminController return View::fetch(); } + + public function addAddonMenu(array $menu,int $pid = 0, int $type = 1) + { + foreach ($menu as $v){ + $hasChild = isset($v['sublist']) && $v['sublist'] ? true : false; + try { + $v['pid'] = $pid; + $v['name'] = trim($v['name'],'/'); + $v['type'] = $type; + $menu = AuthRule::withTrashed()->where('name',$v['name'])->find(); + if($menu){ + $menu->restore(); + } else { + $menu = AuthRule::create($v); + } + + if ($hasChild) { + $this->addAddonMenu($v['sublist'], $menu->id,$type); + } + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + } + + //循环删除菜单 + public function delAddonMenu(array $menu,string $module = 'addon') + { + foreach ($menu as $k=>$v){ + $hasChild = isset($v['sublist']) && $v['sublist'] ? true : false; + try { + $v['name'] = trim($v['name'],'/'); + $menu_rule = AuthRule::withTrashed()->where('name',$v['name'])->find(); + if($menu_rule){ + $menu_rule->delete(true); + if ($hasChild) { + $this->delAddonMenu($v['sublist']); + } + } + + } catch (\Exception $e) { + throw new \Exception($e->getMessage()); + } + } + + } + } diff --git a/app/admin/controller/AuthRule.php b/app/admin/controller/AuthRule.php index 72a5edd..08da4c5 100644 --- a/app/admin/controller/AuthRule.php +++ b/app/admin/controller/AuthRule.php @@ -2,6 +2,7 @@ namespace app\admin\controller; use app\common\controller\AdminController; +use think\App; use think\facade\Request; use think\facade\Db; use think\facade\View; @@ -9,27 +10,20 @@ use app\admin\model\AuthRule as AuthRuleModel; class AuthRule extends AdminController { + // + public function __construct(App $app) + { + parent::__construct($app); + $this->model = new AuthRuleModel(); + } + /** * 菜单列表 */ public function index() { if(Request::isAjax()){ - - $rule = new AuthRuleModel(); - $auth_rules = $rule->authRuleTree(); - $count = count($auth_rules); - $res = []; - if($auth_rules){ - $res = ['code'=>0,'msg'=>'ok','count'=>$count]; - - foreach($auth_rules as $k => $v){ - //$data = $v->getData(); - $data = ['id'=>$v['id'],'pid'=>$v['pid'],'title'=>$v['title'],'url'=>$v['name'],'icon'=>$v['icon'],'status'=>$v['status'],'isMenu'=>$v['ishidden'],'sort'=>$v['sort'],'ctime'=>$v['create_time']]; - $res['data'][] = $data; - } - } - return json($res); + return $this->model->getAuthRuleTree(); } return View::fetch(); @@ -88,24 +82,19 @@ class AuthRule extends AdminController { if(Request::isAjax()){ $data = Request::param(); - $plevel = Db::name('auth_rule')->field('level')->find($data['pid']); + + //层级level + $plevel = Db::name('auth_rule')->field('level')->find($data['pid']); if($plevel){ $data['level'] = $plevel['level']+1; } else { $data['level'] = 0; } - $data['create_time'] = time(); - $list = Db::name('auth_rule')->save($data); - - if($list){ - return json(['code'=>0,'msg'=>'添加权限成功']); - }else{ - return json(['code'=>-1,'msg'=>'添加权限失败']); - } + + return $this->model->saveRule($data); } - - $rule = new AuthRuleModel(); - $auth_rules = $rule->authRuleTree(); + + $auth_rules = $this->model->getAuthRuleTree(); View::assign('AuthRule',$auth_rules); return View::fetch(); } @@ -116,33 +105,27 @@ class AuthRule extends AdminController $rule = new AuthRuleModel(); if(Request::isAjax()){ - $data = Request::param(['id','pid','title','name','icon','sort','ishidden']); - $ruId = $rule->find($data['pid']); //查询出上级ID + $data = Request::param(['id','pid','title','name','icon','sort','ismenu']); + //层级level + $ruId = $rule->find($data['pid']); //查询出上级ID if($ruId){ $plevel = $ruId->level; //上级level等级 $data['level'] = $plevel+1; } else { $data['level'] = 0; } - - $zi = $rule->where('pid',$data['id'])->select();//查询出下级 + $zi = $this->model->where('pid',$data['id'])->select();//查询出下级 + if(!empty($zi)){ + $zi->update(['level'=>$data['level']+1]); + } - if($zi){ - $zi->update(['level'=>$data['level']+1]); - } - - $save = AuthRuleModel::update($data); - - if($save){ - $res = ['code'=>0,'msg'=>'修改成功']; - } else { - $res = ['code'=>-1,'msg'=>'修改失败']; - } - return json($res); + $rule = $this->model->find($data['id']); + return $rule->saveRule($data); } - $auth_rules = $rule->authRuleTree(); - $rules = $rule->find(input('id')); + $auth_rules = $this->model->getAuthRuleTree(); + $rules = $this->model->find(input('id')); + View::assign(['AuthRule'=>$auth_rules,'rules'=>$rules]); return View::fetch(); } @@ -174,7 +157,7 @@ class AuthRule extends AdminController $data = Request::param(); $rules = Db::name('auth_rule')->save($data); if($rules){ - if($data['ishidden'] == 1){ + if($data['ismenu'] == 1){ return json(['code'=>0,'msg'=>'设置菜单显示','icon'=>6]); } else { return json(['code'=>0,'msg'=>'取消菜单显示','icon'=>5]); diff --git a/app/admin/controller/Forum.php b/app/admin/controller/Forum.php index 73978a2..d82665c 100644 --- a/app/admin/controller/Forum.php +++ b/app/admin/controller/Forum.php @@ -12,6 +12,7 @@ use think\facade\Db; use think\facade\Cache; use taoler\com\Files; use app\common\lib\Msgres; +use think\response\Json; class Forum extends AdminController { @@ -103,7 +104,7 @@ class Forum extends AdminController if(Request::isAjax()){ $arr = explode(",",$id); foreach($arr as $v){ - $article =Article::find($v); + $article = Article::find($v); $result = $article->together(['comments'])->delete(); } @@ -118,7 +119,7 @@ class Forum extends AdminController /** * 置顶、加精、评论开关,审核等状态管理 * - * @return void + * @return Json */ public function check() { @@ -137,18 +138,9 @@ class Forum extends AdminController //帖子分类 public function tags() { + $cate = new Cate(); if(Request::isAjax()){ - $list = Cate::select(); - if($list){ - $res['code'] = 0; - $res['msg'] = ''; - $res['count']= count($list); - $res['data'] = []; - foreach($list as $k=>$v){ - $res['data'][] = ['sort'=>$v['sort'],'id' => $v['id'],'pid'=>$v['pid'],'tags'=>$v['catename'],'ename'=>$v['ename'],'detpl'=>$v['detpl'],'icon'=>$v['icon'],'is_hot'=>$v['is_hot'],'desc'=>$v['desc']]; - } - } - return json($res); + return $cate->getList(); } //详情模板 $sys = $this->getSystem(); @@ -482,20 +474,19 @@ class Forum extends AdminController /** * 调用百度关键词 * - * @return void + * @return json */ public function getKeywords() { - $data = Request::only(['tags','flag']); + $data = Request::only(['flag','keywords','content']); return $this->setKeywords($data); } - /** - * 标题调用百度关键词词条 - * - * @return void - */ + /** + * 标题调用百度关键词词条 + * @return Json + */ public function getWordList() { $title = input('title'); @@ -507,7 +498,8 @@ class Forum extends AdminController * 内容中是否有图片视频音频插入 * * @param [type] $content - * @return boolean + * @return array + * */ public function hasIva($content) { diff --git a/app/admin/controller/Slider.php b/app/admin/controller/Slider.php index f3208a4..e1eb8aa 100644 --- a/app/admin/controller/Slider.php +++ b/app/admin/controller/Slider.php @@ -44,7 +44,7 @@ class Slider extends AdminController } if(count($datas)) { - $list = ['code'=>0,'msg'=>'获取数据成功']; + $list = ['code'=>0, 'count'=> count($datas), 'msg'=>'获取数据成功']; foreach($datas as $k=>$v) { $list['data'][] = [ 'id'=>$v['id'], diff --git a/app/admin/controller/Upgrade.php b/app/admin/controller/Upgrade.php index 5175dd6..0a508a1 100644 --- a/app/admin/controller/Upgrade.php +++ b/app/admin/controller/Upgrade.php @@ -168,9 +168,7 @@ class Upgrade extends AdminController $package_file = $upload_dir.'taoler_'.$version_num.'.zip'; //升级的压缩包文件 $cpfile = copy($file_url,$package_file); - - if(!$cpfile) - { + if(!$cpfile) { return json(['code'=>-1,'msg'=>'下载升级文件失败']); } //记录下日志 @@ -236,15 +234,12 @@ class Upgrade extends AdminController private function execute_update(string $package_file) { //解压 zip文件有密码的话需要解密 - //$uzip = new ZipFile(); $zip = new Zip; $zipDir = strstr($package_file, '.zip',true); //返回文件名后缀前的字符串 $zipPath = Files::getDirPath($zipDir); //转换为带/的路径 压缩文件解压到的路径 - //$unzip_res = $uzip->unzip($package_file,$zipPath,true); $unzip_res = $zip->unzip($package_file,$zipPath); - if(!$unzip_res) - { + if(!$unzip_res) { return json(['code'=>-1,'msg'=>'解压失败']); } //解压成功,得到文件夹 @@ -253,8 +248,7 @@ class Upgrade extends AdminController Log::channel('update')->info('update:{type} {progress} {msg}',['type'=>'success','progress'=>'50%','msg'=>'升级文件解压成功!']); //升级PHP - if(is_dir($zipPath)) - { + if(is_dir($zipPath)) { //升级前的写入文件权限检查 $allUpdateFiles = Files::getAllFile($zipPath); @@ -276,8 +270,7 @@ class Upgrade extends AdminController $cpRes = Files::copyDirs($zipPath,$this->root_dir); $cpData = $cpRes->getData(); //更新失败 - if($cpData['code'] == -1) - { + if($cpData['code'] == -1) { //数据库回滚 /* if(file_exists($this->upload_dir.'/'.$package_file.'/mysql/mysql_rockback.sql')) @@ -297,13 +290,8 @@ class Upgrade extends AdminController //升级sql操作 $upSql = $zipPath.'runtime/update.sql'; - if(file_exists($upSql)) - { - $sqlRes = $this->db_update($upSql); - $upDate = $sqlRes->getData(); - if($upDate['code'] == -1){ - return json(['code'=>-1,'msg'=>$upDate['msg']]); - } + if(file_exists($upSql)) { + SqlFile::dbExecute($upSql); //删除sql语句 unlink($upSql); } @@ -317,7 +305,7 @@ class Upgrade extends AdminController } /** - * 处理升级包上传 + * 手动处理升级包上传 */ public function uploadZip() { @@ -373,36 +361,4 @@ class Upgrade extends AdminController } - /** - * 数据库操作 - */ - public function database_operation($file) - { - $mysqli = new \mysqli('localhost','root','root','test'); - if($mysqli->connect_errno) - { - return json(['code'=>0,'msg'=>'Connect failed:'.$mysqli->connect_error]); - } - $sql = file_get_contents($file); - $a = $mysqli->multi_query($sql); - return ['code'=>1,'msg'=>'数据库操作OK']; - } - - /** - * 执行数据库操作 - */ - public function db_update($file) - { - $sqlf = new SqlFile(); - $sql_array = $sqlf->load_sql_file($file); - foreach($sql_array as $v){ - $sqlRes = Db::execute($v); - if ($sqlRes === false) { - return json(['code'=>-1,'msg'=>'数据库升级失败']); - } - } - return json(['code'=>0,'msg'=>'数据库升级成功']); - } - - } \ No newline at end of file diff --git a/app/admin/model/AuthRule.php b/app/admin/model/AuthRule.php index 786d2d7..3909c6d 100644 --- a/app/admin/model/AuthRule.php +++ b/app/admin/model/AuthRule.php @@ -16,29 +16,36 @@ class AuthRule extends Model { $query->where('id', $value ); } - /** - * 权限树 - */ - public function authRuleTree() + + /** + * 获取权限列表 + * @return \think\response\Json + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\DbException + * @throws \think\db\exception\ModelNotFoundException + */ + public function getAuthRuleTree() { - $authRules = $this->order('sort asc')->select(); - //return $this->sort($authRules); - return $authRules; - } - /** - * id,pid,菜单排序 - * @var $data 数据 - * @var $pid 父级id - */ - public function sort($data,$pid=0) - { - static $arr = array(); - foreach($data as $k=> $v){ - if($v['pid']==$pid){ - $arr[] = $v; - $this->sort($data,$v['id']); - } - } - return $arr; - } + $authRules = $this->field('id,pid,title,name,icon,status,ismenu,sort,create_time')->select()->toArray(); + //数组排序 + $cmf_arr = array_column($authRules, 'sort'); + array_multisort($cmf_arr, SORT_ASC, $authRules); + + if(count($authRules)) { + return json(['code'=>0,'msg'=>'ok','data'=>$authRules]); + } else { + return json(['code'=>0,'msg'=>'no data','data'=>'']); + } + } + + public function saveRule($data) + { + $res = $this->save($data); + if($res){ + return json(['code'=>0,'msg'=>'添加权限成功']); + }else{ + return json(['code'=>-1,'msg'=>'添加权限失败']); + } + } + } diff --git a/app/admin/view/auth_rule/add.html b/app/admin/view/auth_rule/add.html index 8ffb5d4..8ceef9a 100644 --- a/app/admin/view/auth_rule/add.html +++ b/app/admin/view/auth_rule/add.html @@ -38,9 +38,9 @@
- - - + + +
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 @@
- +
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 = << +
+ QueryList官网 + 这是图片 + 这是图片2 +
+ 其它的一些文本 +
+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, ">", $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>"; + } + + 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 简介 +`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 cafeEX +- 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 +`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 )}
diff --git a/view/taoler/index/article/blog/detail.html b/view/taoler/index/article/blog/detail.html index 092ac55..0e3ed79 100644 --- a/view/taoler/index/article/blog/detail.html +++ b/view/taoler/index/article/blog/detail.html @@ -162,7 +162,7 @@
评论已关闭!
{/if} - {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 )}
diff --git a/view/taoler/index/article/posts/detail.html b/view/taoler/index/article/posts/detail.html index c080bba..febd5af 100644 --- a/view/taoler/index/article/posts/detail.html +++ b/view/taoler/index/article/posts/detail.html @@ -130,7 +130,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 )}
diff --git a/view/taoler/index/public/base.html b/view/taoler/index/public/base.html index a18d9a8..b972de6 100644 --- a/view/taoler/index/public/base.html +++ b/view/taoler/index/public/base.html @@ -32,7 +32,7 @@ - + {block name="link"}{/block} diff --git a/view/taoler/index/public/column.html b/view/taoler/index/public/column.html index 604155b..8230818 100644 --- a/view/taoler/index/public/column.html +++ b/view/taoler/index/public/column.html @@ -5,7 +5,7 @@ {:lang('home page')} {volist name="subcatelist" id="vo"} -
  • +
  • {:cookie('think_lang') == 'en-us' ? $vo.ename : $vo.catename}{if($vo.is_hot eq 1)}{/if}
  • {/volist} diff --git a/view/taoler/index/public/crud.html b/view/taoler/index/public/crud.html index 2fe2679..b19fea5 100644 --- a/view/taoler/index/public/crud.html +++ b/view/taoler/index/public/crud.html @@ -1,5 +1,5 @@ {//管理帖子加精、置顶、编辑、删除模块} -{if (($article.upzip !== '') || session('?user_name'))} +{if (session('?user_name'))}
    {if ($user.auth ?? '')} @@ -22,8 +22,7 @@ {/if} {if(session('user_name')==$article.user.name || ($user.auth ?? ''))} - - + {/if}
    diff --git a/view/taoler/index/public/footer.html b/view/taoler/index/public/footer.html index 4375ebd..2a3088e 100644 --- a/view/taoler/index/public/footer.html +++ b/view/taoler/index/public/footer.html @@ -9,8 +9,9 @@ {/volist} {//websocket统计脚本} diff --git a/view/taoler/index/user/home.html b/view/taoler/index/user/home.html index 51f7de0..f73f866 100644 --- a/view/taoler/index/user/home.html +++ b/view/taoler/index/user/home.html @@ -68,7 +68,7 @@
  • {$vo.create_time|date='Y-m-d H-m'} - {:lang('in')}{$vo.title}{:lang('reply')}: + {:lang('in')}{$vo.title}{:lang('reply')}:

    {$vo.content}