Merge branches 'dev' and 'master' of https://github.com/wisp-x/lsky-pro into dev
@@ -16,7 +16,8 @@
|
||||
[](http://php.net)
|
||||
[](https://github.com/wisp-x/lsky-pro)
|
||||
|
||||
> 下载稳定版请点击[这里](https://github.com/wisp-x/lsky-pro/releases),发现 bug 可发送邮件至邮箱:i@wispx.cn,或提交 [issues](https://github.com/wisp-x/lsky-pro/issues),确认 bug 后我会及时修复,谢谢!
|
||||
> 下载稳定版请点击[这里](https://github.com/wisp-x/lsky-pro/releases),发现 bug 可发送邮件至邮箱:i@wispx.cn,或提交 [issues](https://github.com/wisp-x/lsky-pro/issues)
|
||||
> 下载速度慢的可以移步 Coding https://dev.tencent.com/u/wispx/p/lsky-pro-releases/git
|
||||
|
||||

|
||||

|
||||
|
||||
@@ -35,7 +35,7 @@ class Base extends Controller
|
||||
$this->response('API is not open yet.', [], 500);
|
||||
}
|
||||
|
||||
$this->token = $this->request->header('token');
|
||||
$this->token = $this->request->header('token', $this->param('token'));
|
||||
$this->auth($this->token);
|
||||
|
||||
$format = $this->param('format');
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by WispX.
|
||||
* User: WispX <1591788658@qq.com>
|
||||
* Date: 2019/10/31
|
||||
* Time: 11:10 上午
|
||||
* Link: https://github.com/wisp-x
|
||||
*/
|
||||
|
||||
namespace app\api\controller;
|
||||
|
||||
use app\common\model\Images;
|
||||
use app\index\controller\User;
|
||||
|
||||
class Image extends Base
|
||||
{
|
||||
private $model;
|
||||
|
||||
public function initialize()
|
||||
{
|
||||
parent::initialize();
|
||||
$this->model = new Images();
|
||||
$this->model = $this->model->where('user_id', $this->user->id)->field(['user_id', 'folder_id'], true);
|
||||
}
|
||||
|
||||
public function find()
|
||||
{
|
||||
$id = $this->param('id');
|
||||
$image = $this->model->where(['id' => $id])->find();
|
||||
$this->response('success', $this->parseData($image));
|
||||
}
|
||||
|
||||
public function items()
|
||||
{
|
||||
$page = $this->param('page', 1);
|
||||
$rows = $this->param('rows', 20);
|
||||
$images = $this->model->paginate(null, false, [
|
||||
'page' => $page,
|
||||
'list_rows' => $rows,
|
||||
])->each(function ($item) {
|
||||
$item = $this->parseData($item);
|
||||
unset($item['create_time']);
|
||||
return $item;
|
||||
});
|
||||
$this->response('success', $images);
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
$user = new User();
|
||||
$data = str_replace(',', ',', $this->param('id'));
|
||||
if (strpos($data, ',') !== false) {
|
||||
$data = explode(',', $data);
|
||||
}
|
||||
if ($user->deleteImages($data)) {
|
||||
return $this->response('删除成功!');
|
||||
}
|
||||
return $this->response('删除失败!', [], 500);
|
||||
}
|
||||
|
||||
private function parseData($data)
|
||||
{
|
||||
$data['upload_time'] = $data->getData('create_time');
|
||||
$data['upload_date'] = $data->create_time;
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ class Images extends Model
|
||||
|
||||
protected $insert = ['ip'];
|
||||
|
||||
protected $append = ['url'];
|
||||
|
||||
public function getUrlAttr($url, $data)
|
||||
{
|
||||
// 图片链接
|
||||
|
||||
@@ -47,55 +47,53 @@ class User extends Base
|
||||
|
||||
public function deleteImages($deleteId = null)
|
||||
{
|
||||
if ($this->request->isPost()) {
|
||||
Db::startTrans();
|
||||
try {
|
||||
$id = $deleteId ? $deleteId : $this->request->post('id');
|
||||
$deletes = []; // 需要删除的文件
|
||||
if (is_array($id)) {
|
||||
$images = Images::all($id);
|
||||
foreach ($images as &$value) {
|
||||
$deletes[$value->strategy][] = $value->pathname;
|
||||
$value->delete();
|
||||
unset($value);
|
||||
}
|
||||
} else {
|
||||
$image = Images::get($id);
|
||||
if (!$image) {
|
||||
throw new Exception('没有找到该图片数据');
|
||||
}
|
||||
$deletes[$image->strategy][] = $image->pathname;
|
||||
$image->delete();
|
||||
Db::startTrans();
|
||||
try {
|
||||
$id = $deleteId ? $deleteId : $this->request->post('id');
|
||||
$deletes = []; // 需要删除的文件
|
||||
if (is_array($id)) {
|
||||
$images = Images::all($id);
|
||||
foreach ($images as &$value) {
|
||||
$deletes[$value->strategy][] = $value->pathname;
|
||||
$value->delete();
|
||||
unset($value);
|
||||
}
|
||||
} else {
|
||||
$image = Images::get($id);
|
||||
if (!$image) {
|
||||
throw new Exception('没有找到该图片数据');
|
||||
}
|
||||
$deletes[$image->strategy][] = $image->pathname;
|
||||
$image->delete();
|
||||
}
|
||||
// 是否开启软删除(开启了只删除记录,不删除文件)
|
||||
if (!$this->config['soft_delete']) {
|
||||
$strategy = [];
|
||||
// 实例化所有储存策略驱动
|
||||
$strategyAll = array_keys(Config::pull('strategy'));
|
||||
foreach ($strategyAll as $value) {
|
||||
// 获取储存策略驱动
|
||||
$strategy[$value] = $this->getStrategyInstance($value);
|
||||
}
|
||||
// 是否开启软删除(开启了只删除记录,不删除文件)
|
||||
if (!$this->config['soft_delete']) {
|
||||
$strategy = [];
|
||||
// 实例化所有储存策略驱动
|
||||
$strategyAll = array_keys(Config::pull('strategy'));
|
||||
foreach ($strategyAll as $value) {
|
||||
// 获取储存策略驱动
|
||||
$strategy[$value] = $this->getStrategyInstance($value);
|
||||
}
|
||||
|
||||
foreach ($deletes as $key => $val) {
|
||||
if (1 === count($val)) {
|
||||
if (!$strategy[$key]->delete(isset($val[0]) ? $val[0] : null)) {
|
||||
throw new Exception('删除失败');
|
||||
}
|
||||
} else {
|
||||
if (!$strategy[$key]->deletes($val)) {
|
||||
throw new Exception('批量删除失败');
|
||||
}
|
||||
foreach ($deletes as $key => $val) {
|
||||
if (1 === count($val)) {
|
||||
if (!$strategy[$key]->delete(isset($val[0]) ? $val[0] : null)) {
|
||||
throw new Exception('删除失败');
|
||||
}
|
||||
} else {
|
||||
if (!$strategy[$key]->deletes($val)) {
|
||||
throw new Exception('批量删除失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
return $deleteId ? false : $this->error($e->getMessage());
|
||||
}
|
||||
return $deleteId ? true : $this->success('删除成功');
|
||||
Db::commit();
|
||||
} catch (Exception $e) {
|
||||
Db::rollback();
|
||||
return $deleteId ? false : $this->error($e->getMessage());
|
||||
}
|
||||
return $deleteId ? true : $this->success('删除成功');
|
||||
}
|
||||
|
||||
public function createFolder()
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace app\index\controller\admin;
|
||||
|
||||
use app\common\model\Config;
|
||||
use think\Db;
|
||||
use app\common\model\Images;
|
||||
use think\Exception;
|
||||
|
||||
/**
|
||||
@@ -50,14 +51,14 @@ class System extends Base
|
||||
|
||||
public function console()
|
||||
{
|
||||
$storage = Db::name('images')->sum('size');
|
||||
$imagesCount = Db::name('images')->count();
|
||||
$suspiciousImagesCount = Db::name('images')->where('suspicious', 1)->count();
|
||||
$users_count = Db::name('users')->count();
|
||||
$today = Db::name('images')->whereTime('create_time', 'today')->count();
|
||||
$yesterday = Db::name('images')->whereTime('create_time', 'yesterday')->count();
|
||||
$month = Db::name('images')->whereTime('create_time', 'month')->count();
|
||||
$tourists = Db::name('images')->where('user_id', 0)->count();
|
||||
$storage = Images::sum('size');
|
||||
$imagesCount = Images::count();
|
||||
$suspiciousImagesCount = Images::where('suspicious', 1)->count();
|
||||
$users_count = \app\common\model\Users::count();
|
||||
$today = Images::whereTime('create_time', 'today')->count();
|
||||
$yesterday = Images::whereTime('create_time', 'yesterday')->count();
|
||||
$month = Images::whereTime('create_time', 'month')->count();
|
||||
$tourists = Images::where('user_id', 0)->count();
|
||||
|
||||
$this->assign([
|
||||
'storage' => format_size($storage, true), // 占用储存
|
||||
|
||||
@@ -17,6 +17,7 @@ class Update extends Base
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
die;
|
||||
// https://dev.tencent.com/u/wispx/p/lsky-pro-releases/git/raw/master/releases/lsky-pro-1.5.4.zip
|
||||
echo <<<EOT
|
||||
<style>
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
<div class="mdui-col item mdui-m-b-2">
|
||||
<div class="mdui-color-blue mdui-text-center mdui-text-color-white mdui-p-a-2 mdui-shadow-4 mdui-hoverable">
|
||||
<p><i class="mdui-icon material-icons"></i> 占用储存</p>
|
||||
<span class="mdui-text-color-amber">{$storage[0]} <small class="mdui-text-color-white">{$storage[1]}</small></span>
|
||||
<span class="mdui-text-color-amber">{$storage[0]}
|
||||
<small class="mdui-text-color-white">{if $storage[0]}{$storage[1]}{else/}Kb{/if}</small>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mdui-col item mdui-m-b-2">
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{block name="title"}登录 - {$config.site_name}{/block}
|
||||
|
||||
{block name="css"}
|
||||
<link href="/static/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/static/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
{/block}
|
||||
|
||||
{block name="main"}
|
||||
@@ -42,5 +42,5 @@
|
||||
{/block}
|
||||
|
||||
{block name="js"}
|
||||
<script src="/static/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<script src="/static/bootstrap/4.3.1/js/bootstrap.min.js"></script>
|
||||
{/block}
|
||||
@@ -3,7 +3,7 @@
|
||||
{block name="title"}注册 - {$config.site_name}{/block}
|
||||
|
||||
{block name="css"}
|
||||
<link href="/static/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="/static/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
|
||||
{/block}
|
||||
|
||||
{block name="main"}
|
||||
@@ -67,5 +67,5 @@
|
||||
{/block}
|
||||
|
||||
{block name="js"}
|
||||
<script src="/static/bootstrap/3.3.7/js/bootstrap.min.js"></script>
|
||||
<script src="/static/bootstrap/4.3.1/js/bootstrap.min.js"></script>
|
||||
{/block}
|
||||
|
||||
@@ -227,6 +227,301 @@ Content-Type: image/png
|
||||
"time": 1544245931
|
||||
}</pre>
|
||||
</div>
|
||||
|
||||
<div class="mdui-shadow-2 mdui-m-t-2 mdui-m-b-1 mdui-p-a-3 mdui-text-color-red">
|
||||
注意:以下接口均需要 Token
|
||||
</div>
|
||||
<div class="mdui-m-b-2">
|
||||
<h2>3. 获取图片列表</h2>
|
||||
<div class="mdui-row">
|
||||
<div class="mdui-col-md-6">
|
||||
<div class="mdui-table-fluid mdui-m-t-1">
|
||||
<table class="mdui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>功能</td>
|
||||
<td>接口</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>请求方式</td>
|
||||
<td>POST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>{$domain}/api/images</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="mdui-m-t-2 mdui-m-b-1">请求参数</h4>
|
||||
<div class="mdui-table-fluid">
|
||||
<table class="mdui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>参数名称</td>
|
||||
<td>类型</td>
|
||||
<td>是否必须</td>
|
||||
<td>说明</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>page</td>
|
||||
<td>String</td>
|
||||
<td>是</td>
|
||||
<td>页码</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>rows</td>
|
||||
<td>String</td>
|
||||
<td>否</td>
|
||||
<td>每页数量, 默认 20 条</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h4 class="mdui-m-t-2 mdui-m-b-1">返回数据(data)说明</h4>
|
||||
<div class="mdui-table-fluid">
|
||||
<table class="mdui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>参数名称</td>
|
||||
<td>类型</td>
|
||||
<td>实例值</td>
|
||||
<td>说明</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>total</td>
|
||||
<td>Number</td>
|
||||
<td>999</td>
|
||||
<td>数据总量</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>per_page</td>
|
||||
<td>String</td>
|
||||
<td>1</td>
|
||||
<td>每页数量</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>current_page</td>
|
||||
<td>Number</td>
|
||||
<td>1</td>
|
||||
<td>当前所在页码</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>last_page</td>
|
||||
<td>Number</td>
|
||||
<td>999</td>
|
||||
<td>最后一页页码</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h4 class="mdui-m-t-2 mdui-m-b-1">图片数据说明</h4>
|
||||
<div class="mdui-table-fluid">
|
||||
<table class="mdui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>参数名称</td>
|
||||
<td>类型</td>
|
||||
<td>实例值</td>
|
||||
<td>说明</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>id</td>
|
||||
<td>Number</td>
|
||||
<td>1</td>
|
||||
<td>图片ID</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>strategy</td>
|
||||
<td>String</td>
|
||||
<td>oss</td>
|
||||
<td>储存策略, (cos:腾讯云, kodo:七牛云, local:本地, oss:阿里云oss, remote:远程储存, uss:又拍云)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>path</td>
|
||||
<td>String</td>
|
||||
<td>2019/10/31</td>
|
||||
<td>图片所在路径</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>name</td>
|
||||
<td>String</td>
|
||||
<td>929616303ca92.jpg</td>
|
||||
<td>图片名称</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>pathname</td>
|
||||
<td>String</td>
|
||||
<td>2019/10/31/929616303ca92.jpg</td>
|
||||
<td>图片路径+名称</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>size</td>
|
||||
<td>String</td>
|
||||
<td>30405.00</td>
|
||||
<td>图片大小(字节: b)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>mime</td>
|
||||
<td>String</td>
|
||||
<td>image/jpeg</td>
|
||||
<td>图片 mime 类型</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>sha1</td>
|
||||
<td>String</td>
|
||||
<td>0143f7904f12e2a76ff2935f21a771b8adadf961</td>
|
||||
<td>图片 sha1 值</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>md5</td>
|
||||
<td>String</td>
|
||||
<td>e630c1d832f1701b0afe09cfe86a7f2b</td>
|
||||
<td>图片 md5 值</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ip</td>
|
||||
<td>String</td>
|
||||
<td>192.168.0.1</td>
|
||||
<td>上传者 IP</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>suspicious</td>
|
||||
<td>Number</td>
|
||||
<td>0</td>
|
||||
<td>是否是可疑图片, (0:否, 1:是)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>update_time</td>
|
||||
<td>Number</td>
|
||||
<td>1572491936</td>
|
||||
<td>图片上传时间</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>update_date</td>
|
||||
<td>String</td>
|
||||
<td>2019-10-31 11:18:56</td>
|
||||
<td>图片上传日期</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>url</td>
|
||||
<td>String</td>
|
||||
<td>http://domain.com/2019/10/31/929616303ca92.jpg</td>
|
||||
<td>图片链接</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mdui-m-b-2">
|
||||
<h2>4. 获取单张图片</h2>
|
||||
<div class="mdui-row">
|
||||
<div class="mdui-col-md-6">
|
||||
<div class="mdui-table-fluid mdui-m-t-1">
|
||||
<table class="mdui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>功能</td>
|
||||
<td>接口</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>请求方式</td>
|
||||
<td>POST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>{$domain}/api/image</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="mdui-m-t-2 mdui-m-b-1">请求参数</h4>
|
||||
<div class="mdui-table-fluid">
|
||||
<table class="mdui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>参数名称</td>
|
||||
<td>类型</td>
|
||||
<td>是否必须</td>
|
||||
<td>说明</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>id</td>
|
||||
<td>String</td>
|
||||
<td>是</td>
|
||||
<td>图片ID</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h4 class="mdui-m-t-2 mdui-m-b-1">返回数据(data)与第三条相同</h4>
|
||||
</div>
|
||||
|
||||
<div class="mdui-m-b-2">
|
||||
<h2>5. 删除图片</h2>
|
||||
<div class="mdui-row">
|
||||
<div class="mdui-col-md-6">
|
||||
<div class="mdui-table-fluid mdui-m-t-1">
|
||||
<table class="mdui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>功能</td>
|
||||
<td>接口</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>请求方式</td>
|
||||
<td>POST</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>URL</td>
|
||||
<td>{$domain}/api/delete</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="mdui-m-t-2 mdui-m-b-1">请求参数</h4>
|
||||
<div class="mdui-table-fluid">
|
||||
<table class="mdui-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>参数名称</td>
|
||||
<td>类型</td>
|
||||
<td>是否必须</td>
|
||||
<td>说明</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>id</td>
|
||||
<td>String</td>
|
||||
<td>是</td>
|
||||
<td>图片ID, 删除多个使用逗号分隔</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
{/block}
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
<div class="mdui-container">
|
||||
<main>
|
||||
<div class="upload-container">
|
||||
{if $config.notice}
|
||||
<div class="alert alert-info alert-dismissible fade show">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
{$config.notice|raw}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="title">
|
||||
<h1>Image Upload</h1>
|
||||
<p>最大可上传 {:round($config.upload_max_size / 1024 / 1024)} MB的图片,单次同时可选择 {$config.upload_single_num} 张。本站已托管 {$images_count} 张图片。</p>
|
||||
@@ -84,7 +90,7 @@
|
||||
};
|
||||
|
||||
$("#image").fileinput({
|
||||
uploadUrl: "{:url('upload/upload')}",
|
||||
uploadUrl: "{:url('upload/upload')}" + '?rand=' + new Date().getTime().toString(36) + Math.random().toString(36).slice(2),
|
||||
language: "zh",
|
||||
theme: "fas",
|
||||
previewFileType: "image",
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"upyun/sdk": "^3.3",
|
||||
"qcloud/cos-sdk-v5": "^1.2",
|
||||
"topthink/think-image": "^1.0",
|
||||
"phpmailer/phpmailer": "^6.0"
|
||||
"phpmailer/phpmailer": "^6.0",
|
||||
"nicolab/php-ftp-client": "^1.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
||||
@@ -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": "f3a07db17ca2f758f19303939f3bb3d6",
|
||||
"content-hash": "fb6ffef3fc6c84854a1c9c891746c6e1",
|
||||
"packages": [
|
||||
{
|
||||
"name": "aliyuncs/oss-sdk-php",
|
||||
@@ -157,16 +157,16 @@
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "6.3.3",
|
||||
"version": "6.4.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
|
||||
"reference": "0895c932405407fd3a7368b6910c09a24d26db11"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
|
||||
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
|
||||
"reference": "0895c932405407fd3a7368b6910c09a24d26db11",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -176,14 +176,15 @@
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.0",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
"guzzlehttp/psr7": "^1.6.1",
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
|
||||
"psr/log": "^1.0"
|
||||
"psr/log": "^1.1"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
@@ -195,12 +196,12 @@
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
@@ -224,7 +225,7 @@
|
||||
"rest",
|
||||
"web service"
|
||||
],
|
||||
"time": "2018-04-22T15:46:56+00:00"
|
||||
"time": "2019-10-23T15:58:00+00:00"
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/promises",
|
||||
@@ -361,17 +362,72 @@
|
||||
"time": "2019-07-01T23:21:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
"version": "v6.0.7",
|
||||
"name": "nicolab/php-ftp-client",
|
||||
"version": "v1.5.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||
"reference": "0c41a36d4508d470e376498c1c0c527aa36a2d59"
|
||||
"url": "https://github.com/Nicolab/php-ftp-client.git",
|
||||
"reference": "8c66e1104da1b638f5d7a9e24624a5525b459a0c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/0c41a36d4508d470e376498c1c0c527aa36a2d59",
|
||||
"reference": "0c41a36d4508d470e376498c1c0c527aa36a2d59",
|
||||
"url": "https://api.github.com/repos/Nicolab/php-ftp-client/zipball/8c66e1104da1b638f5d7a9e24624a5525b459a0c",
|
||||
"reference": "8c66e1104da1b638f5d7a9e24624a5525b459a0c",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"ext-ftp": "*",
|
||||
"php": ">=5.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"FtpClient": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Tallefourtane",
|
||||
"email": "dev@nicolab.net",
|
||||
"homepage": "http://nicolab.net"
|
||||
}
|
||||
],
|
||||
"description": "A flexible FTP and SSL-FTP client for PHP. This lib provides helpers easy to use to manage the remote files.",
|
||||
"homepage": "https://github.com/Nicolab/php-ftp-client",
|
||||
"keywords": [
|
||||
"file",
|
||||
"ftp",
|
||||
"helper",
|
||||
"lib",
|
||||
"server",
|
||||
"sftp",
|
||||
"ssl",
|
||||
"ssl-ftp"
|
||||
],
|
||||
"time": "2019-04-23T09:22:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
"version": "v6.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||
"reference": "26bd96350b0b2fcbf0ef4e6f0f9cf3528302a9d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/26bd96350b0b2fcbf0ef4e6f0f9cf3528302a9d8",
|
||||
"reference": "26bd96350b0b2fcbf0ef4e6f0f9cf3528302a9d8",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -410,17 +466,17 @@
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
"LGPL-2.1-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Jagielski",
|
||||
"email": "jimjag@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Marcus Bointon",
|
||||
"email": "phpmailer@synchromedia.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Jim Jagielski",
|
||||
"email": "jimjag@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Andy Prevost",
|
||||
"email": "codeworxtech@users.sourceforge.net"
|
||||
@@ -430,7 +486,7 @@
|
||||
}
|
||||
],
|
||||
"description": "PHPMailer is a full-featured email creation and transfer class for PHP",
|
||||
"time": "2019-02-01T15:04:28+00:00"
|
||||
"time": "2019-09-27T21:33:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
@@ -490,16 +546,16 @@
|
||||
},
|
||||
{
|
||||
"name": "qcloud/cos-sdk-v5",
|
||||
"version": "v1.3.2",
|
||||
"version": "v1.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tencentyun/cos-php-sdk-v5.git",
|
||||
"reference": "0454f48629210749ae6316ab317548169dac9d8f"
|
||||
"reference": "1b32aa422f6dffe4ea411e5095e4b0da9135551b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/tencentyun/cos-php-sdk-v5/zipball/0454f48629210749ae6316ab317548169dac9d8f",
|
||||
"reference": "0454f48629210749ae6316ab317548169dac9d8f",
|
||||
"url": "https://api.github.com/repos/tencentyun/cos-php-sdk-v5/zipball/1b32aa422f6dffe4ea411e5095e4b0da9135551b",
|
||||
"reference": "1b32aa422f6dffe4ea411e5095e4b0da9135551b",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -538,20 +594,20 @@
|
||||
"php",
|
||||
"qcloud"
|
||||
],
|
||||
"time": "2019-04-25T12:23:41+00:00"
|
||||
"time": "2019-09-02T12:08:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "qiniu/php-sdk",
|
||||
"version": "v7.2.9",
|
||||
"version": "v7.2.10",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/qiniu/php-sdk.git",
|
||||
"reference": "afe7d8715d8a688b1d8d8cdf031240d2363dad90"
|
||||
"reference": "d89987163f560ebf9dfa5bb25de9bd9b1a3b2bd8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/qiniu/php-sdk/zipball/afe7d8715d8a688b1d8d8cdf031240d2363dad90",
|
||||
"reference": "afe7d8715d8a688b1d8d8cdf031240d2363dad90",
|
||||
"url": "https://api.github.com/repos/qiniu/php-sdk/zipball/d89987163f560ebf9dfa5bb25de9bd9b1a3b2bd8",
|
||||
"reference": "d89987163f560ebf9dfa5bb25de9bd9b1a3b2bd8",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -595,7 +651,7 @@
|
||||
"sdk",
|
||||
"storage"
|
||||
],
|
||||
"time": "2019-07-09T07:55:07+00:00"
|
||||
"time": "2019-10-28T10:23:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "ralouphie/getallheaders",
|
||||
@@ -711,16 +767,16 @@
|
||||
},
|
||||
{
|
||||
"name": "topthink/framework",
|
||||
"version": "v5.1.37.1",
|
||||
"version": "v5.1.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/top-think/framework.git",
|
||||
"reference": "05eecd121d18d6705aaa10aa44fcdf7c14da4d0b"
|
||||
"reference": "12d15c29d5d6a972fc8bfc8db005d64d4786028c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/top-think/framework/zipball/05eecd121d18d6705aaa10aa44fcdf7c14da4d0b",
|
||||
"reference": "05eecd121d18d6705aaa10aa44fcdf7c14da4d0b",
|
||||
"url": "https://api.github.com/repos/top-think/framework/zipball/12d15c29d5d6a972fc8bfc8db005d64d4786028c",
|
||||
"reference": "12d15c29d5d6a972fc8bfc8db005d64d4786028c",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -764,7 +820,7 @@
|
||||
"orm",
|
||||
"thinkphp"
|
||||
],
|
||||
"time": "2019-05-28T06:57:29+00:00"
|
||||
"time": "2019-08-12T00:58:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "topthink/think-captcha",
|
||||
@@ -906,16 +962,16 @@
|
||||
},
|
||||
{
|
||||
"name": "upyun/sdk",
|
||||
"version": "3.3.0",
|
||||
"version": "3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/upyun/php-sdk.git",
|
||||
"reference": "1a2dd5ae31047956c733aef0f764f3a527d30628"
|
||||
"reference": "b4819fd941e3f19a886f8b3c5f8bffcb8279185f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/upyun/php-sdk/zipball/1a2dd5ae31047956c733aef0f764f3a527d30628",
|
||||
"reference": "1a2dd5ae31047956c733aef0f764f3a527d30628",
|
||||
"url": "https://api.github.com/repos/upyun/php-sdk/zipball/b4819fd941e3f19a886f8b3c5f8bffcb8279185f",
|
||||
"reference": "b4819fd941e3f19a886f8b3c5f8bffcb8279185f",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -945,10 +1001,6 @@
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "totoleo",
|
||||
"email": "totoleo@163.com"
|
||||
},
|
||||
{
|
||||
"name": "lfeng",
|
||||
"email": "bonevv@gmail.com"
|
||||
@@ -957,6 +1009,10 @@
|
||||
"name": "lvtongda",
|
||||
"email": "riyao.lyu@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "totoleo",
|
||||
"email": "totoleo@163.com"
|
||||
},
|
||||
{
|
||||
"name": "sabakugaara",
|
||||
"email": "senellise@gmail.com"
|
||||
@@ -968,7 +1024,7 @@
|
||||
"sdk",
|
||||
"upyun"
|
||||
],
|
||||
"time": "2017-11-12T09:17:42+00:00"
|
||||
"time": "2019-04-29T09:27:51+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
|
||||
@@ -8,13 +8,10 @@
|
||||
|
||||
// [命名规则配置文件]
|
||||
|
||||
$array = gettimeofday();
|
||||
$aa = ($array['sec'] * 100000 + $array['usec'] / 10) & 0x7FFFFFFF;
|
||||
$logId = ((($array['sec'] * 100000 + $array['usec'] / 10) & 0x7FFFFFFF) | 0x80000000);
|
||||
|
||||
$time = time();
|
||||
$md5 = md5('LSKY PRO' . $logId . time());
|
||||
$uniqid = substr(md5($logId), 8, 13);
|
||||
$rand = function_exists('session_create_id') ? session_create_id() : uniqid();
|
||||
$md5 = md5('LSKY PRO' . $rand . time());
|
||||
$uniqid = substr(md5($rand), 8, 13);
|
||||
|
||||
return [
|
||||
'path' => [
|
||||
|
||||
@@ -29,4 +29,8 @@ return [
|
||||
'name' => '又拍云USS',
|
||||
'class' => \strategy\driver\Uss::class
|
||||
],
|
||||
'remote' => [
|
||||
'name' => '远程',
|
||||
'class' => \strategy\driver\Remote::class
|
||||
],
|
||||
];
|
||||
|
||||
@@ -16,6 +16,8 @@ namespace strategy;
|
||||
*/
|
||||
interface Driver
|
||||
{
|
||||
public function __construct($options = []);
|
||||
|
||||
/**
|
||||
* 创建文件
|
||||
*
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Created by WispX.
|
||||
* User: WispX <1591788658@qq.com>
|
||||
* Date: 2019/10/31
|
||||
* Time: 9:39 上午
|
||||
* Link: https://github.com/wisp-x
|
||||
*/
|
||||
|
||||
namespace strategy\driver;
|
||||
|
||||
use FtpClient\FtpException;
|
||||
use strategy\Driver;
|
||||
|
||||
class Remote implements Driver
|
||||
{
|
||||
private $ftp;
|
||||
|
||||
private $error;
|
||||
|
||||
public function __construct($options = [])
|
||||
{
|
||||
try {
|
||||
$this->ftp = new \FtpClient\FtpClient();
|
||||
$this->ftp->connect($options['remote_host']);
|
||||
$this->ftp->login($options['remote_name'], $options['remote_password']);
|
||||
} catch (FtpException $e) {
|
||||
$this->error = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件
|
||||
*
|
||||
* @param $pathname
|
||||
* @param $file
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function create($pathname, $file)
|
||||
{
|
||||
try {
|
||||
$dirname = dirname($pathname);
|
||||
if (!$this->ftp->isDir($dirname)) {
|
||||
if (!$this->ftp->mkdir($dirname, true)) {
|
||||
throw new FtpException('文件夹创建失败!');
|
||||
}
|
||||
}
|
||||
if (!$this->ftp->put($pathname, $file, FTP_BINARY)) {
|
||||
throw new FtpException('上传失败');
|
||||
}
|
||||
} catch (FtpException $e) {
|
||||
$this->error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除单个文件
|
||||
*
|
||||
* @param $pathname
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function delete($pathname)
|
||||
{
|
||||
try {
|
||||
if (!$this->ftp->remove($pathname, true)) {
|
||||
throw new FtpException('删除失败');
|
||||
}
|
||||
} catch (FtpException $e) {
|
||||
$this->error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除多个文件
|
||||
*
|
||||
* @param array $list 一维数组
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function deletes(array $list)
|
||||
{
|
||||
try {
|
||||
foreach ($list as $item) {
|
||||
if (!$this->ftp->remove($item, true)) {
|
||||
throw new FtpException('删除失败');
|
||||
}
|
||||
}
|
||||
} catch (FtpException $e) {
|
||||
$this->error = $e->getMessage();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取出错信息
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getError()
|
||||
{
|
||||
return 'Remote:' . $this->error;
|
||||
}
|
||||
}
|
||||
@@ -153,11 +153,18 @@ INSERT INTO `lsky_config` (`id`, `key`, `type`, `input_type`, `name`, `title`, `
|
||||
(NULL, 'uss', 'text', 'text', 'uss_operator_name', 'OperatorName', '操作员账号', '', ''),
|
||||
(NULL, 'uss', 'text', 'password', 'uss_operator_pwd', 'OperatorPwd', '操作员密码', '', ''),
|
||||
(NULL, 'uss', 'text', 'text', 'uss_service_name', 'ServiceName', '云储存服务名称', '', ''),
|
||||
(NULL, 'basics', 'textarea', 'textarea', 'notice', '系统公告', '支持html', '', ''),
|
||||
(NULL, 'remote', 'text', 'text', 'remote_cdn_domain', '域名', NULL, '', ''),
|
||||
(NULL, 'remote', 'select', 'text', 'remote_type', '远程储存类型', NULL, 'ftp', '{\"ftp\":\"ftp\"}'),
|
||||
(NULL, 'remote', 'text', 'text', 'remote_host', '连接地址', NULL, '', ''),
|
||||
(NULL, 'remote', 'text', 'text', 'remote_name', '登录账号', NULL, '', ''),
|
||||
(NULL, 'remote', 'text', 'password', 'remote_password', '登录密码', NULL, '', ''),
|
||||
(NULL, 'remote', 'text', 'number', 'remote_port', '连接端口', NULL, '21', ''),
|
||||
|
||||
(NULL, 'audit', 'bool', 'checkbox', 'open_audit', '开启图片鉴黄', '接口申请地址:<a href="https://www.moderatecontent.com" target="_blank">https://www.moderatecontent.com</a>', '0', ''),
|
||||
(NULL, 'audit', 'text', 'text', 'audit_key', 'Key', NULL, '', ''),
|
||||
(NULL, 'audit', 'select', 'text', 'audit_index', '内容评级', '1=所有人,2=少年,3=成人', '3', '{\"1\": \"所有人\", \"2\": \"少年\", \"3\": \"成人\"}'),
|
||||
|
||||
(NULL, '', 'text', 'text', 'system_version', '系统版本', NULL, '1.5.4', '');
|
||||
(NULL, '', 'text', 'text', 'system_version', '系统版本', NULL, '1.5.5', '');
|
||||
|
||||
INSERT INTO `lsky_group` (`id`, `strategy`, `name`, `default`, `update_time`, `create_time`) VALUES (NULL, 'local', '默认组', '1', '0', '0');
|
||||
|
||||
|
Before Width: | Height: | Size: 447 KiB |
|
Before Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 383 KiB |
|
Before Width: | Height: | Size: 891 KiB |
|
Before Width: | Height: | Size: 1.3 MiB |
|
Before Width: | Height: | Size: 393 KiB |
|
Before Width: | Height: | Size: 683 KiB |
|
Before Width: | Height: | Size: 456 KiB |
|
Before Width: | Height: | Size: 437 KiB |
@@ -15,8 +15,11 @@ Route::view('compatibility', 'index@tpl/compatibility');
|
||||
|
||||
// [Api Route]
|
||||
Route::group('api', function () {
|
||||
Route::post('token', 'api/Token/index');
|
||||
Route::post('upload', 'api/Upload/index');
|
||||
Route::any('token', 'api/Token/index');
|
||||
Route::any('upload', 'api/Upload/index');
|
||||
Route::any('image', 'api/Image/find');
|
||||
Route::any('images', 'api/Image/items');
|
||||
Route::any('delete', 'api/Image/delete');
|
||||
})
|
||||
->header('Access-Control-Allow-Headers', 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With, Token')
|
||||
->allowCrossDomain();
|
||||
|
||||
@@ -686,7 +686,13 @@ if (!function_exists('widget')) {
|
||||
*/
|
||||
function widget($name, $data = [])
|
||||
{
|
||||
return app()->action($name, $data, 'widget');
|
||||
$result = app()->action($name, $data, 'widget');
|
||||
|
||||
if (is_object($result)) {
|
||||
$result = $result->getContent();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use think\route\Dispatch;
|
||||
*/
|
||||
class App extends Container
|
||||
{
|
||||
const VERSION = '5.1.37 LTS';
|
||||
const VERSION = '5.1.38 LTS';
|
||||
|
||||
/**
|
||||
* 当前模块路径
|
||||
|
||||
@@ -353,7 +353,7 @@ class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSeria
|
||||
$result = isset($data[$field]) ? $data[$field] : null;
|
||||
}
|
||||
|
||||
switch ($operator) {
|
||||
switch (strtolower($operator)) {
|
||||
case '===':
|
||||
return $result === $value;
|
||||
case '!==':
|
||||
|
||||
@@ -158,7 +158,7 @@ class Controller
|
||||
*/
|
||||
protected function fetch($template = '', $vars = [], $config = [])
|
||||
{
|
||||
return $this->view->fetch($template, $vars, $config);
|
||||
return Response::create($template, 'view')->assign($vars)->config($config);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,7 +171,7 @@ class Controller
|
||||
*/
|
||||
protected function display($content = '', $vars = [], $config = [])
|
||||
{
|
||||
return $this->view->display($content, $vars, $config);
|
||||
return Response::create($content, 'view')->assign($vars)->config($config)->isContent(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -682,6 +682,7 @@ class Request
|
||||
// 判断URL里面是否有兼容模式参数
|
||||
$pathinfo = $_GET[$this->config['var_pathinfo']];
|
||||
unset($_GET[$this->config['var_pathinfo']]);
|
||||
unset($this->get[$this->config['var_pathinfo']]);
|
||||
} elseif ($this->isCli()) {
|
||||
// CLI模式下 index.php module/controller/action/params/...
|
||||
$pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : '';
|
||||
@@ -702,6 +703,10 @@ class Request
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($pathinfo)) {
|
||||
unset($this->get[$pathinfo], $this->request[$pathinfo]);
|
||||
}
|
||||
|
||||
$this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/');
|
||||
}
|
||||
|
||||
@@ -1039,7 +1044,7 @@ class Request
|
||||
|
||||
protected function getInputData($content)
|
||||
{
|
||||
if (false !== strpos($this->contentType(), 'application/json') || 0 === strpos($content, '{"')) {
|
||||
if ($this->isJson()) {
|
||||
return (array) json_decode($content, true);
|
||||
} elseif (strpos($content, '=')) {
|
||||
parse_str($content, $data);
|
||||
@@ -1631,6 +1636,19 @@ class Request
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前是否JSON请求
|
||||
* @access public
|
||||
* @return bool
|
||||
*/
|
||||
public function isJson()
|
||||
{
|
||||
$contentType = $this->contentType();
|
||||
$acceptType = $this->type();
|
||||
|
||||
return false !== strpos($contentType, 'json') || false !== strpos($acceptType, 'json');
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前是否Ajax请求
|
||||
* @access public
|
||||
|
||||
@@ -130,7 +130,9 @@ class Url
|
||||
// 匹配路由命名标识
|
||||
$url = $match[0];
|
||||
|
||||
$domain = $match[1];
|
||||
if ($domain) {
|
||||
$domain = $match[1];
|
||||
}
|
||||
|
||||
if (!is_null($match[2])) {
|
||||
$suffix = $match[2];
|
||||
|
||||
@@ -160,7 +160,10 @@ class View
|
||||
*/
|
||||
public function filter($filter)
|
||||
{
|
||||
$this->filter = $filter;
|
||||
if ($filter) {
|
||||
$this->filter = $filter;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
@@ -407,7 +407,7 @@ abstract class Builder
|
||||
$jsonType = $query->getJsonFieldType($field);
|
||||
$bindType = $this->connection->getFieldBindType($jsonType);
|
||||
} else {
|
||||
$bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR;
|
||||
$bindType = isset($binds[$field]) && 'LIKE' != $exp ? $binds[$field] : PDO::PARAM_STR;
|
||||
}
|
||||
|
||||
if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) {
|
||||
@@ -450,7 +450,7 @@ abstract class Builder
|
||||
// 模糊匹配
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $item) {
|
||||
$name = $query->bind($item, $bindType);
|
||||
$name = $query->bind($item, PDO::PARAM_STR);
|
||||
$array[] = $key . ' ' . $exp . ' :' . $name;
|
||||
}
|
||||
|
||||
@@ -604,6 +604,10 @@ abstract class Builder
|
||||
$value = $this->parseClosure($query, $value);
|
||||
}
|
||||
|
||||
if ('=' == $exp && is_null($value)) {
|
||||
return $key . ' IS NULL';
|
||||
}
|
||||
|
||||
return $key . ' ' . $exp . ' ' . $value;
|
||||
}
|
||||
|
||||
@@ -651,7 +655,6 @@ abstract class Builder
|
||||
$value = $value->getValue();
|
||||
} else {
|
||||
$value = array_unique(is_array($value) ? $value : explode(',', $value));
|
||||
|
||||
$array = [];
|
||||
|
||||
foreach ($value as $k => $v) {
|
||||
@@ -659,9 +662,12 @@ abstract class Builder
|
||||
$array[] = ':' . $name;
|
||||
}
|
||||
|
||||
$zone = implode(',', $array);
|
||||
|
||||
$value = empty($zone) ? "''" : $zone;
|
||||
if (count($array) == 1) {
|
||||
return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0];
|
||||
} else {
|
||||
$zone = implode(',', $array);
|
||||
$value = empty($zone) ? "''" : $zone;
|
||||
}
|
||||
}
|
||||
|
||||
return $key . ' ' . $exp . ' (' . $value . ')';
|
||||
|
||||
@@ -1467,9 +1467,7 @@ abstract class Connection
|
||||
$value = is_array($val) ? $val[0] : $val;
|
||||
$type = is_array($val) ? $val[1] : PDO::PARAM_STR;
|
||||
|
||||
if (self::PARAM_FLOAT == $type) {
|
||||
$value = (float) $value;
|
||||
} elseif (PDO::PARAM_STR == $type) {
|
||||
if ((self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) && is_string($value)) {
|
||||
$value = '\'' . addslashes($value) . '\'';
|
||||
} elseif (PDO::PARAM_INT == $type && '' === $value) {
|
||||
$value = 0;
|
||||
@@ -1503,7 +1501,7 @@ abstract class Connection
|
||||
if (PDO::PARAM_INT == $val[1] && '' === $val[0]) {
|
||||
$val[0] = 0;
|
||||
} elseif (self::PARAM_FLOAT == $val[1]) {
|
||||
$val[0] = (float) $val[0];
|
||||
$val[0] = is_string($val[0]) ? (float) $val[0] : $val[0];
|
||||
$val[1] = PDO::PARAM_STR;
|
||||
}
|
||||
|
||||
|
||||
@@ -95,14 +95,14 @@ class Query
|
||||
* @var array
|
||||
*/
|
||||
protected $timeRule = [
|
||||
'today' => ['today', 'tomorrow'],
|
||||
'yesterday' => ['yesterday', 'today'],
|
||||
'week' => ['this week 00:00:00', 'next week 00:00:00'],
|
||||
'last week' => ['last week 00:00:00', 'this week 00:00:00'],
|
||||
'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'],
|
||||
'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'],
|
||||
'year' => ['this year 1/1', 'next year 1/1'],
|
||||
'last year' => ['last year 1/1', 'this year 1/1'],
|
||||
'today' => ['today', 'tomorrow -1second'],
|
||||
'yesterday' => ['yesterday', 'today -1second'],
|
||||
'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'],
|
||||
'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'],
|
||||
'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'],
|
||||
'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'],
|
||||
'year' => ['this year 1/1', 'next year 1/1 -1second'],
|
||||
'last year' => ['last year 1/1', 'this year 1/1 -1second'],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -129,7 +129,7 @@ class Mysql extends Builder
|
||||
// JSON字段支持
|
||||
list($field, $name) = explode('->', $key, 2);
|
||||
|
||||
return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$.' . str_replace('->', '.', $name) . '\')';
|
||||
return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')';
|
||||
} elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) {
|
||||
list($table, $key) = explode('.', $key, 2);
|
||||
|
||||
|
||||
@@ -136,7 +136,27 @@ class Mysql extends Connection
|
||||
*/
|
||||
protected function getExplain($sql)
|
||||
{
|
||||
$pdo = $this->linkID->query("EXPLAIN " . $sql);
|
||||
$pdo = $this->linkID->prepare("EXPLAIN " . $this->queryStr);
|
||||
|
||||
foreach ($this->bind as $key => $val) {
|
||||
// 占位符
|
||||
$param = is_int($key) ? $key + 1 : ':' . $key;
|
||||
|
||||
if (is_array($val)) {
|
||||
if (PDO::PARAM_INT == $val[1] && '' === $val[0]) {
|
||||
$val[0] = 0;
|
||||
} elseif (self::PARAM_FLOAT == $val[1]) {
|
||||
$val[0] = is_string($val[0]) ? (float) $val[0] : $val[0];
|
||||
$val[1] = PDO::PARAM_STR;
|
||||
}
|
||||
|
||||
$result = $pdo->bindValue($param, $val[0], $val[1]);
|
||||
} else {
|
||||
$result = $pdo->bindValue($param, $val);
|
||||
}
|
||||
}
|
||||
|
||||
$pdo->execute();
|
||||
$result = $pdo->fetch(PDO::FETCH_ASSOC);
|
||||
$result = array_change_key_case($result);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ class ValidateException extends \RuntimeException
|
||||
public function __construct($error, $code = 0)
|
||||
{
|
||||
$this->error = $error;
|
||||
$this->message = is_array($error) ? implode("\n\r", $error) : $error;
|
||||
$this->message = is_array($error) ? implode(PHP_EOL, $error) : $error;
|
||||
$this->code = $code;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ class File
|
||||
$info['timestamp'] = date($this->config['time_format']);
|
||||
|
||||
foreach ($message as $type => $msg) {
|
||||
$msg = is_array($msg) ? implode("\r\n", $msg) : $msg;
|
||||
$msg = is_array($msg) ? implode(PHP_EOL, $msg) : $msg;
|
||||
if (PHP_SAPI == 'cli') {
|
||||
$info['msg'] = $msg;
|
||||
$info['type'] = $type;
|
||||
@@ -212,14 +212,14 @@ class File
|
||||
protected function parseCliLog($info)
|
||||
{
|
||||
if ($this->config['json']) {
|
||||
$message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n";
|
||||
$message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
|
||||
} else {
|
||||
$now = $info['timestamp'];
|
||||
unset($info['timestamp']);
|
||||
|
||||
$message = implode("\r\n", $info);
|
||||
$message = implode(PHP_EOL, $info);
|
||||
|
||||
$message = "[{$now}]" . $message . "\r\n";
|
||||
$message = "[{$now}]" . $message . PHP_EOL;
|
||||
}
|
||||
|
||||
return $message;
|
||||
@@ -242,13 +242,13 @@ class File
|
||||
|
||||
if ($this->config['json']) {
|
||||
$info = $requestInfo + $info;
|
||||
return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n";
|
||||
return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL;
|
||||
}
|
||||
|
||||
array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}");
|
||||
array_unshift($info, "---------------------------------------------------------------" . PHP_EOL . "\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}");
|
||||
unset($info['timestamp']);
|
||||
|
||||
return implode("\r\n", $info) . "\r\n";
|
||||
return implode(PHP_EOL, $info) . PHP_EOL;
|
||||
}
|
||||
|
||||
protected function getDebugLog(&$info, $append, $apart)
|
||||
|
||||
@@ -189,10 +189,12 @@ trait Conversion
|
||||
|
||||
if (!$relation) {
|
||||
$relation = $this->getAttr($key);
|
||||
$relation->visible($name);
|
||||
if ($relation) {
|
||||
$relation->visible($name);
|
||||
}
|
||||
}
|
||||
|
||||
$item[$key] = $relation->append($name)->toArray();
|
||||
$item[$key] = $relation ? $relation->append($name)->toArray() : [];
|
||||
} elseif (strpos($name, '.')) {
|
||||
list($key, $attr) = explode('.', $name);
|
||||
// 追加关联对象属性
|
||||
@@ -200,10 +202,12 @@ trait Conversion
|
||||
|
||||
if (!$relation) {
|
||||
$relation = $this->getAttr($key);
|
||||
$relation->visible([$attr]);
|
||||
if ($relation) {
|
||||
$relation->visible([$attr]);
|
||||
}
|
||||
}
|
||||
|
||||
$item[$key] = $relation->append([$attr])->toArray();
|
||||
$item[$key] = $relation ? $relation->append([$attr])->toArray() : [];
|
||||
} else {
|
||||
$item[$name] = $this->getAttr($name, $item);
|
||||
}
|
||||
|
||||
@@ -140,13 +140,17 @@ class BelongsTo extends OneToOne
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) {
|
||||
$query->table([$table => $relation])
|
||||
->field($relation . '.' . $localKey)
|
||||
->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey);
|
||||
->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -167,12 +171,16 @@ class BelongsTo extends OneToOne
|
||||
$this->getQueryWhere($where, $relation);
|
||||
}
|
||||
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->field($fields)
|
||||
->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->where($where);
|
||||
}
|
||||
|
||||
|
||||
@@ -292,14 +292,18 @@ class HasMany extends Relation
|
||||
*/
|
||||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
|
||||
{
|
||||
$table = $this->query->getTable();
|
||||
$model = basename(str_replace('\\', '/', get_class($this->parent)));
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
$table = $this->query->getTable();
|
||||
$model = basename(str_replace('\\', '/', get_class($this->parent)));
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->field($model . '.*')
|
||||
->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->group($relation . '.' . $this->foreignKey)
|
||||
->having('count(' . $id . ')' . $operator . $count);
|
||||
}
|
||||
@@ -321,13 +325,17 @@ class HasMany extends Relation
|
||||
$this->getQueryWhere($where, $relation);
|
||||
}
|
||||
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->group($model . '.' . $this->localKey)
|
||||
->field($fields)
|
||||
->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->where($where);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
namespace think\model\relation;
|
||||
|
||||
use think\db\Query;
|
||||
use think\Exception;
|
||||
use think\Loader;
|
||||
use think\Model;
|
||||
use think\model\Relation;
|
||||
@@ -24,6 +23,12 @@ class HasManyThrough extends Relation
|
||||
// 中间表模型
|
||||
protected $through;
|
||||
|
||||
/**
|
||||
* 中间主键
|
||||
* @var string
|
||||
*/
|
||||
protected $throughPk;
|
||||
|
||||
/**
|
||||
* 架构函数
|
||||
* @access public
|
||||
@@ -38,9 +43,10 @@ class HasManyThrough extends Relation
|
||||
{
|
||||
$this->parent = $parent;
|
||||
$this->model = $model;
|
||||
$this->through = $through;
|
||||
$this->through = (new $through)->db();
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->throughKey = $throughKey;
|
||||
$this->throughPk = $this->through->getPk();
|
||||
$this->localKey = $localKey;
|
||||
$this->query = (new $model)->db();
|
||||
}
|
||||
@@ -74,7 +80,28 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER')
|
||||
{
|
||||
return $this->parent;
|
||||
$model = App::parseName(App::classBaseName($this->parent));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$relation = (new $this->model)->db();
|
||||
$relationTable = $relation->getTable();
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
if ('*' != $id) {
|
||||
$id = $relationTable . '.' . $relation->getPk();
|
||||
}
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->field($model . '.*')
|
||||
->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey)
|
||||
->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relationTable) {
|
||||
$query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->group($relationTable . '.' . $this->throughKey)
|
||||
->having('count(' . $id . ')' . $operator . $count);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,45 +113,225 @@ class HasManyThrough extends Relation
|
||||
*/
|
||||
public function hasWhere($where = [], $fields = null)
|
||||
{
|
||||
throw new Exception('relation not support: hasWhere');
|
||||
$model = App::parseName(App::classBaseName($this->parent));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = (new $this->model)->db()->getTable();
|
||||
|
||||
if (is_array($where)) {
|
||||
$this->getQueryWhere($where, $modelTable);
|
||||
}
|
||||
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey)
|
||||
->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk)
|
||||
->when($softDelete, function ($query) use ($softDelete, $modelTable) {
|
||||
$query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->group($modelTable . '.' . $this->throughKey)
|
||||
->where($where)
|
||||
->field($fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询
|
||||
* @access public
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* 预载入关联查询(数据集)
|
||||
* @access protected
|
||||
* @param array $resultSet 数据集
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure)
|
||||
{}
|
||||
public function eagerlyResultSet(array &$resultSet, $relation, array $subRelation = [], $closure = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
|
||||
$range = [];
|
||||
foreach ($resultSet as $result) {
|
||||
// 获取关联外键列表
|
||||
if (isset($result->$localKey)) {
|
||||
$range[] = $result->$localKey;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($range)) {
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$this->foreignKey, 'in', $range],
|
||||
], $foreignKey, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联属性名
|
||||
$attr = App::parseName($relation);
|
||||
|
||||
// 关联数据封装
|
||||
foreach ($resultSet as $result) {
|
||||
$pk = $result->$localKey;
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
foreach ($data[$pk] as &$relationModel) {
|
||||
$relationModel->setParent(clone $result);
|
||||
}
|
||||
|
||||
// 设置关联属性
|
||||
$result->setRelation($attr, $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预载入关联查询 返回模型对象
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param string $subRelation 子关联名
|
||||
* @param \Closure $closure 闭包
|
||||
* 预载入关联查询(数据)
|
||||
* @access protected
|
||||
* @param Model $result 数据对象
|
||||
* @param string $relation 当前关联名
|
||||
* @param array $subRelation 子关联名
|
||||
* @param Closure $closure 闭包
|
||||
* @return void
|
||||
*/
|
||||
public function eagerlyResult(&$result, $relation, $subRelation, $closure)
|
||||
{}
|
||||
public function eagerlyResult($result, $relation, array $subRelation = [], $closure = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
$pk = $result->$localKey;
|
||||
|
||||
$this->query->removeWhereField($foreignKey);
|
||||
|
||||
$data = $this->eagerlyWhere([
|
||||
[$foreignKey, '=', $pk],
|
||||
], $foreignKey, $relation, $subRelation, $closure);
|
||||
|
||||
// 关联数据封装
|
||||
if (!isset($data[$pk])) {
|
||||
$data[$pk] = [];
|
||||
}
|
||||
|
||||
foreach ($data[$pk] as &$relationModel) {
|
||||
$relationModel->setParent(clone $result);
|
||||
}
|
||||
|
||||
$result->setRelation(App::parseName($relation), $this->resultSetBuild($data[$pk]));
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联模型预查询
|
||||
* @access public
|
||||
* @param array $where 关联预查询条件
|
||||
* @param string $key 关联键名
|
||||
* @param string $relation 关联名
|
||||
* @param array $subRelation 子关联
|
||||
* @param Closure $closure
|
||||
* @return array
|
||||
*/
|
||||
protected function eagerlyWhere(array $where, $key, $relation, array $subRelation = [], $closure = null)
|
||||
{
|
||||
// 预载入关联查询 支持嵌套预载入
|
||||
$throughList = $this->through->where($where)->select();
|
||||
$keys = $throughList->column($this->throughPk, $this->throughPk);
|
||||
|
||||
if ($closure) {
|
||||
$closure($this->query);
|
||||
}
|
||||
|
||||
$list = $this->query->where($this->throughKey, 'in', $keys)->select();
|
||||
|
||||
// 组装模型数据
|
||||
$data = [];
|
||||
$keys = $throughList->column($this->foreignKey, $this->throughPk);
|
||||
|
||||
foreach ($list as $set) {
|
||||
$data[$keys[$set->{$this->throughKey}]][] = $set;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关联统计
|
||||
* @access public
|
||||
* @param Model $result 数据对象
|
||||
* @param \Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @param Model $result 数据对象
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return integer
|
||||
*/
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '')
|
||||
{}
|
||||
public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = null)
|
||||
{
|
||||
$localKey = $this->localKey;
|
||||
|
||||
if (!isset($result->$localKey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($closure) {
|
||||
$return = $closure($this->query);
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
$alias = App::parseName(App::classBaseName($this->model));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = $this->parent->getTable();
|
||||
|
||||
if (false === strpos($field, '.')) {
|
||||
$field = $alias . '.' . $field;
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->alias($alias)
|
||||
->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
|
||||
->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
|
||||
->where($throughTable . '.' . $this->foreignKey, $result->$localKey)
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建关联统计子查询
|
||||
* @access public
|
||||
* @param Closure $closure 闭包
|
||||
* @param string $aggregate 聚合查询方法
|
||||
* @param string $field 字段
|
||||
* @param string $name 统计字段别名
|
||||
* @return string
|
||||
*/
|
||||
public function getRelationCountQuery($closure = null, $aggregate = 'count', $field = '*', &$name = null)
|
||||
{
|
||||
if ($closure) {
|
||||
$return = $closure($this->query);
|
||||
if ($return && is_string($return)) {
|
||||
$name = $return;
|
||||
}
|
||||
}
|
||||
|
||||
$alias = App::parseName(App::classBaseName($this->model));
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = $this->parent->getTable();
|
||||
|
||||
if (false === strpos($field, '.')) {
|
||||
$field = $alias . '.' . $field;
|
||||
}
|
||||
|
||||
return $this->query
|
||||
->alias($alias)
|
||||
->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey)
|
||||
->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey)
|
||||
->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey)
|
||||
->fetchSql()
|
||||
->$aggregate($field);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行基础查询(仅执行一次)
|
||||
@@ -134,10 +341,9 @@ class HasManyThrough extends Relation
|
||||
protected function baseQuery()
|
||||
{
|
||||
if (empty($this->baseQuery) && $this->parent->getData()) {
|
||||
$through = $this->through;
|
||||
$alias = Loader::parseName(basename(str_replace('\\', '/', $this->model)));
|
||||
$throughTable = $through::getTable();
|
||||
$pk = (new $through)->getPk();
|
||||
$throughTable = $this->through->getTable();
|
||||
$pk = $this->throughPk;
|
||||
$throughKey = $this->throughKey;
|
||||
$modelTable = $this->parent->getTable();
|
||||
$fields = $this->getQueryFields($alias);
|
||||
|
||||
@@ -139,13 +139,17 @@ class HasOne extends OneToOne
|
||||
$relation = basename(str_replace('\\', '/', $this->model));
|
||||
$localKey = $this->localKey;
|
||||
$foreignKey = $this->foreignKey;
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) {
|
||||
$query->table([$table => $relation])
|
||||
->field($relation . '.' . $foreignKey)
|
||||
->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey);
|
||||
->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -166,12 +170,16 @@ class HasOne extends OneToOne
|
||||
$this->getQueryWhere($where, $relation);
|
||||
}
|
||||
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$fields = $this->getRelationQueryFields($fields, $model);
|
||||
$softDelete = $this->query->getOptions('soft_delete');
|
||||
|
||||
return $this->parent->db()
|
||||
->alias($model)
|
||||
->field($fields)
|
||||
->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType)
|
||||
->when($softDelete, function ($query) use ($softDelete, $relation) {
|
||||
$query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null);
|
||||
})
|
||||
->where($where);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,16 @@ class View extends Response
|
||||
// 输出参数
|
||||
protected $options = [];
|
||||
protected $vars = [];
|
||||
protected $config = [];
|
||||
protected $filter;
|
||||
protected $contentType = 'text/html';
|
||||
|
||||
/**
|
||||
* 是否内容渲染
|
||||
* @var bool
|
||||
*/
|
||||
protected $isContent = false;
|
||||
|
||||
/**
|
||||
* 处理数据
|
||||
* @access protected
|
||||
@@ -32,7 +39,19 @@ class View extends Response
|
||||
// 渲染模板输出
|
||||
return $this->app['view']
|
||||
->filter($this->filter)
|
||||
->fetch($data, $this->vars);
|
||||
->fetch($data, $this->vars, $this->config, $this->isContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为内容渲染
|
||||
* @access public
|
||||
* @param bool $content
|
||||
* @return $this
|
||||
*/
|
||||
public function isContent($content = true)
|
||||
{
|
||||
$this->isContent = $content;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -68,6 +87,12 @@ class View extends Response
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function config($config)
|
||||
{
|
||||
$this->config = $config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 视图内容过滤
|
||||
* @access public
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
namespace think\route;
|
||||
|
||||
use think\App;
|
||||
use think\Container;
|
||||
use think\exception\ValidateException;
|
||||
use think\App;
|
||||
use think\Request;
|
||||
use think\Response;
|
||||
|
||||
@@ -181,9 +181,10 @@ abstract class Dispatch
|
||||
|
||||
$response = Response::create($data, $type);
|
||||
} else {
|
||||
$data = ob_get_clean();
|
||||
$content = false === $data ? '' : $data;
|
||||
$status = '' === $content && $this->request->isAjax() ? 204 : 200;
|
||||
$data = ob_get_clean();
|
||||
$content = false === $data ? '' : $data;
|
||||
$status = '' === $content && $this->request->isJson() ? 204 : 200;
|
||||
|
||||
$response = Response::create($content, '', $status);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
namespace think\route\dispatch;
|
||||
|
||||
use ReflectionMethod;
|
||||
use think\Controller;
|
||||
use think\exception\ClassNotFoundException;
|
||||
use think\exception\HttpException;
|
||||
use think\Loader;
|
||||
|
||||
@@ -76,3 +76,14 @@ UPDATE `lsky_config` SET `value` = '1.5.3' WHERE `lsky_config`.`name` = 'system_
|
||||
|
||||
-- v1.5.4
|
||||
UPDATE `lsky_config` SET `value` = '1.5.4' WHERE `lsky_config`.`name` = 'system_version';
|
||||
|
||||
-- v1.5.5
|
||||
UPDATE `lsky_config` SET `value` = '1.5.5' WHERE `lsky_config`.`name` = 'system_version';
|
||||
INSERT IGNORE INTO `lsky_config` (`id`, `key`, `type`, `input_type`, `name`, `title`, `tip`, `value`, `extend`) VALUES
|
||||
(NULL, 'basics', 'textarea', 'textarea', 'notice', '系统公告', '支持html', '', ''),
|
||||
(NULL, 'remote', 'text', 'text', 'remote_cdn_domain', '域名', NULL, '', ''),
|
||||
(NULL, 'remote', 'select', 'text', 'remote_type', '远程储存类型', NULL, 'ftp', '{\"ftp\":\"Ftp\"}'),
|
||||
(NULL, 'remote', 'text', 'text', 'remote_host', '连接地址', NULL, '', ''),
|
||||
(NULL, 'remote', 'text', 'text', 'remote_name', '登录账号', NULL, '', ''),
|
||||
(NULL, 'remote', 'text', 'password', 'remote_password', '登录密码', NULL, '', ''),
|
||||
(NULL, 'remote', 'text', 'number', 'remote_port', '连接端口', NULL, '21', '');
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
||||
return ComposerAutoloaderInit04f78adc0d26d025ab398ddde054e232::getLoader();
|
||||
return ComposerAutoloaderInitf1a511e38c2f284964a16f1eeccf1745::getLoader();
|
||||
|
||||
@@ -9,4 +9,5 @@ return array(
|
||||
'Qcloud\\Cos\\' => array($vendorDir . '/qcloud/cos-sdk-v5/src'),
|
||||
'Guzzle\\Tests' => array($vendorDir . '/guzzle/guzzle/tests'),
|
||||
'Guzzle' => array($vendorDir . '/guzzle/guzzle/src'),
|
||||
'FtpClient' => array($vendorDir . '/nicolab/php-ftp-client/src'),
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
// autoload_real.php @generated by Composer
|
||||
|
||||
class ComposerAutoloaderInit04f78adc0d26d025ab398ddde054e232
|
||||
class ComposerAutoloaderInitf1a511e38c2f284964a16f1eeccf1745
|
||||
{
|
||||
private static $loader;
|
||||
|
||||
@@ -19,15 +19,15 @@ class ComposerAutoloaderInit04f78adc0d26d025ab398ddde054e232
|
||||
return self::$loader;
|
||||
}
|
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit04f78adc0d26d025ab398ddde054e232', 'loadClassLoader'), true, true);
|
||||
spl_autoload_register(array('ComposerAutoloaderInitf1a511e38c2f284964a16f1eeccf1745', 'loadClassLoader'), true, true);
|
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader();
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInit04f78adc0d26d025ab398ddde054e232', 'loadClassLoader'));
|
||||
spl_autoload_unregister(array('ComposerAutoloaderInitf1a511e38c2f284964a16f1eeccf1745', 'loadClassLoader'));
|
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
|
||||
if ($useStaticLoader) {
|
||||
require_once __DIR__ . '/autoload_static.php';
|
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit04f78adc0d26d025ab398ddde054e232::getInitializer($loader));
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInitf1a511e38c2f284964a16f1eeccf1745::getInitializer($loader));
|
||||
} else {
|
||||
$map = require __DIR__ . '/autoload_namespaces.php';
|
||||
foreach ($map as $namespace => $path) {
|
||||
@@ -48,19 +48,19 @@ class ComposerAutoloaderInit04f78adc0d26d025ab398ddde054e232
|
||||
$loader->register(true);
|
||||
|
||||
if ($useStaticLoader) {
|
||||
$includeFiles = Composer\Autoload\ComposerStaticInit04f78adc0d26d025ab398ddde054e232::$files;
|
||||
$includeFiles = Composer\Autoload\ComposerStaticInitf1a511e38c2f284964a16f1eeccf1745::$files;
|
||||
} else {
|
||||
$includeFiles = require __DIR__ . '/autoload_files.php';
|
||||
}
|
||||
foreach ($includeFiles as $fileIdentifier => $file) {
|
||||
composerRequire04f78adc0d26d025ab398ddde054e232($fileIdentifier, $file);
|
||||
composerRequiref1a511e38c2f284964a16f1eeccf1745($fileIdentifier, $file);
|
||||
}
|
||||
|
||||
return $loader;
|
||||
}
|
||||
}
|
||||
|
||||
function composerRequire04f78adc0d26d025ab398ddde054e232($fileIdentifier, $file)
|
||||
function composerRequiref1a511e38c2f284964a16f1eeccf1745($fileIdentifier, $file)
|
||||
{
|
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
|
||||
require $file;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
namespace Composer\Autoload;
|
||||
|
||||
class ComposerStaticInit04f78adc0d26d025ab398ddde054e232
|
||||
class ComposerStaticInitf1a511e38c2f284964a16f1eeccf1745
|
||||
{
|
||||
public static $files = array (
|
||||
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
|
||||
@@ -129,14 +129,21 @@ class ComposerStaticInit04f78adc0d26d025ab398ddde054e232
|
||||
0 => __DIR__ . '/..' . '/guzzle/guzzle/src',
|
||||
),
|
||||
),
|
||||
'F' =>
|
||||
array (
|
||||
'FtpClient' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/nicolab/php-ftp-client/src',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
public static function getInitializer(ClassLoader $loader)
|
||||
{
|
||||
return \Closure::bind(function () use ($loader) {
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit04f78adc0d26d025ab398ddde054e232::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInit04f78adc0d26d025ab398ddde054e232::$prefixDirsPsr4;
|
||||
$loader->prefixesPsr0 = ComposerStaticInit04f78adc0d26d025ab398ddde054e232::$prefixesPsr0;
|
||||
$loader->prefixLengthsPsr4 = ComposerStaticInitf1a511e38c2f284964a16f1eeccf1745::$prefixLengthsPsr4;
|
||||
$loader->prefixDirsPsr4 = ComposerStaticInitf1a511e38c2f284964a16f1eeccf1745::$prefixDirsPsr4;
|
||||
$loader->prefixesPsr0 = ComposerStaticInitf1a511e38c2f284964a16f1eeccf1745::$prefixesPsr0;
|
||||
|
||||
}, null, ClassLoader::class);
|
||||
}
|
||||
|
||||
@@ -154,17 +154,17 @@
|
||||
},
|
||||
{
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"version": "6.3.3",
|
||||
"version_normalized": "6.3.3.0",
|
||||
"version": "6.4.1",
|
||||
"version_normalized": "6.4.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/guzzle/guzzle.git",
|
||||
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
|
||||
"reference": "0895c932405407fd3a7368b6910c09a24d26db11"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
|
||||
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
|
||||
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/0895c932405407fd3a7368b6910c09a24d26db11",
|
||||
"reference": "0895c932405407fd3a7368b6910c09a24d26db11",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -174,19 +174,20 @@
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.0",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
"guzzlehttp/psr7": "^1.6.1",
|
||||
"php": ">=5.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
|
||||
"psr/log": "^1.0"
|
||||
"psr/log": "^1.1"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"time": "2018-04-22T15:46:56+00:00",
|
||||
"time": "2019-10-23T15:58:00+00:00",
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
@@ -195,12 +196,12 @@
|
||||
},
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
@@ -364,18 +365,75 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
"version": "v6.0.7",
|
||||
"version_normalized": "6.0.7.0",
|
||||
"name": "nicolab/php-ftp-client",
|
||||
"version": "v1.5.1",
|
||||
"version_normalized": "1.5.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||
"reference": "0c41a36d4508d470e376498c1c0c527aa36a2d59"
|
||||
"url": "https://github.com/Nicolab/php-ftp-client.git",
|
||||
"reference": "8c66e1104da1b638f5d7a9e24624a5525b459a0c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/0c41a36d4508d470e376498c1c0c527aa36a2d59",
|
||||
"reference": "0c41a36d4508d470e376498c1c0c527aa36a2d59",
|
||||
"url": "https://api.github.com/repos/Nicolab/php-ftp-client/zipball/8c66e1104da1b638f5d7a9e24624a5525b459a0c",
|
||||
"reference": "8c66e1104da1b638f5d7a9e24624a5525b459a0c",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%",
|
||||
"preferred": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"require": {
|
||||
"ext-ftp": "*",
|
||||
"php": ">=5.4"
|
||||
},
|
||||
"time": "2019-04-23T09:22:37+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
"psr-0": {
|
||||
"FtpClient": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Tallefourtane",
|
||||
"email": "dev@nicolab.net",
|
||||
"homepage": "http://nicolab.net"
|
||||
}
|
||||
],
|
||||
"description": "A flexible FTP and SSL-FTP client for PHP. This lib provides helpers easy to use to manage the remote files.",
|
||||
"homepage": "https://github.com/Nicolab/php-ftp-client",
|
||||
"keywords": [
|
||||
"file",
|
||||
"ftp",
|
||||
"helper",
|
||||
"lib",
|
||||
"server",
|
||||
"sftp",
|
||||
"ssl",
|
||||
"ssl-ftp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "phpmailer/phpmailer",
|
||||
"version": "v6.1.1",
|
||||
"version_normalized": "6.1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PHPMailer/PHPMailer.git",
|
||||
"reference": "26bd96350b0b2fcbf0ef4e6f0f9cf3528302a9d8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/26bd96350b0b2fcbf0ef4e6f0f9cf3528302a9d8",
|
||||
"reference": "26bd96350b0b2fcbf0ef4e6f0f9cf3528302a9d8",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -406,7 +464,7 @@
|
||||
"stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication",
|
||||
"symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)"
|
||||
},
|
||||
"time": "2019-02-01T15:04:28+00:00",
|
||||
"time": "2019-09-27T21:33:43+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
@@ -416,17 +474,17 @@
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"LGPL-2.1"
|
||||
"LGPL-2.1-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Jim Jagielski",
|
||||
"email": "jimjag@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Marcus Bointon",
|
||||
"email": "phpmailer@synchromedia.co.uk"
|
||||
},
|
||||
{
|
||||
"name": "Jim Jagielski",
|
||||
"email": "jimjag@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Andy Prevost",
|
||||
"email": "codeworxtech@users.sourceforge.net"
|
||||
@@ -497,17 +555,17 @@
|
||||
},
|
||||
{
|
||||
"name": "qcloud/cos-sdk-v5",
|
||||
"version": "v1.3.2",
|
||||
"version_normalized": "1.3.2.0",
|
||||
"version": "v1.3.4",
|
||||
"version_normalized": "1.3.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tencentyun/cos-php-sdk-v5.git",
|
||||
"reference": "0454f48629210749ae6316ab317548169dac9d8f"
|
||||
"reference": "1b32aa422f6dffe4ea411e5095e4b0da9135551b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/tencentyun/cos-php-sdk-v5/zipball/0454f48629210749ae6316ab317548169dac9d8f",
|
||||
"reference": "0454f48629210749ae6316ab317548169dac9d8f",
|
||||
"url": "https://api.github.com/repos/tencentyun/cos-php-sdk-v5/zipball/1b32aa422f6dffe4ea411e5095e4b0da9135551b",
|
||||
"reference": "1b32aa422f6dffe4ea411e5095e4b0da9135551b",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -520,7 +578,7 @@
|
||||
"guzzle/guzzle": "~3.7",
|
||||
"php": ">=5.3.0"
|
||||
},
|
||||
"time": "2019-04-25T12:23:41+00:00",
|
||||
"time": "2019-09-02T12:08:44+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
@@ -551,17 +609,17 @@
|
||||
},
|
||||
{
|
||||
"name": "qiniu/php-sdk",
|
||||
"version": "v7.2.9",
|
||||
"version_normalized": "7.2.9.0",
|
||||
"version": "v7.2.10",
|
||||
"version_normalized": "7.2.10.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/qiniu/php-sdk.git",
|
||||
"reference": "afe7d8715d8a688b1d8d8cdf031240d2363dad90"
|
||||
"reference": "d89987163f560ebf9dfa5bb25de9bd9b1a3b2bd8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/qiniu/php-sdk/zipball/afe7d8715d8a688b1d8d8cdf031240d2363dad90",
|
||||
"reference": "afe7d8715d8a688b1d8d8cdf031240d2363dad90",
|
||||
"url": "https://api.github.com/repos/qiniu/php-sdk/zipball/d89987163f560ebf9dfa5bb25de9bd9b1a3b2bd8",
|
||||
"reference": "d89987163f560ebf9dfa5bb25de9bd9b1a3b2bd8",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -577,7 +635,7 @@
|
||||
"phpunit/phpunit": "~4.0",
|
||||
"squizlabs/php_codesniffer": "~2.3"
|
||||
},
|
||||
"time": "2019-07-09T07:55:07+00:00",
|
||||
"time": "2019-10-28T10:23:23+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
@@ -726,17 +784,17 @@
|
||||
},
|
||||
{
|
||||
"name": "topthink/framework",
|
||||
"version": "v5.1.37.1",
|
||||
"version_normalized": "5.1.37.1",
|
||||
"version": "v5.1.38.1",
|
||||
"version_normalized": "5.1.38.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/top-think/framework.git",
|
||||
"reference": "05eecd121d18d6705aaa10aa44fcdf7c14da4d0b"
|
||||
"reference": "12d15c29d5d6a972fc8bfc8db005d64d4786028c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/top-think/framework/zipball/05eecd121d18d6705aaa10aa44fcdf7c14da4d0b",
|
||||
"reference": "05eecd121d18d6705aaa10aa44fcdf7c14da4d0b",
|
||||
"url": "https://api.github.com/repos/top-think/framework/zipball/12d15c29d5d6a972fc8bfc8db005d64d4786028c",
|
||||
"reference": "12d15c29d5d6a972fc8bfc8db005d64d4786028c",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -758,7 +816,7 @@
|
||||
"sebastian/phpcpd": "2.*",
|
||||
"squizlabs/php_codesniffer": "2.*"
|
||||
},
|
||||
"time": "2019-05-28T06:57:29+00:00",
|
||||
"time": "2019-08-12T00:58:30+00:00",
|
||||
"type": "think-framework",
|
||||
"installation-source": "dist",
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@@ -929,17 +987,17 @@
|
||||
},
|
||||
{
|
||||
"name": "upyun/sdk",
|
||||
"version": "3.3.0",
|
||||
"version_normalized": "3.3.0.0",
|
||||
"version": "3.4.0",
|
||||
"version_normalized": "3.4.0.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/upyun/php-sdk.git",
|
||||
"reference": "1a2dd5ae31047956c733aef0f764f3a527d30628"
|
||||
"reference": "b4819fd941e3f19a886f8b3c5f8bffcb8279185f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/upyun/php-sdk/zipball/1a2dd5ae31047956c733aef0f764f3a527d30628",
|
||||
"reference": "1a2dd5ae31047956c733aef0f764f3a527d30628",
|
||||
"url": "https://api.github.com/repos/upyun/php-sdk/zipball/b4819fd941e3f19a886f8b3c5f8bffcb8279185f",
|
||||
"reference": "b4819fd941e3f19a886f8b3c5f8bffcb8279185f",
|
||||
"shasum": "",
|
||||
"mirrors": [
|
||||
{
|
||||
@@ -958,7 +1016,7 @@
|
||||
"phpdocumentor/phpdocumentor": "^2.9",
|
||||
"phpunit/phpunit": "~4.0"
|
||||
},
|
||||
"time": "2017-11-12T09:17:42+00:00",
|
||||
"time": "2019-04-29T09:27:51+00:00",
|
||||
"type": "library",
|
||||
"installation-source": "dist",
|
||||
"autoload": {
|
||||
@@ -971,10 +1029,6 @@
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "totoleo",
|
||||
"email": "totoleo@163.com"
|
||||
},
|
||||
{
|
||||
"name": "lfeng",
|
||||
"email": "bonevv@gmail.com"
|
||||
@@ -983,6 +1037,10 @@
|
||||
"name": "lvtongda",
|
||||
"email": "riyao.lyu@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "totoleo",
|
||||
"email": "totoleo@163.com"
|
||||
},
|
||||
{
|
||||
"name": "sabakugaara",
|
||||
"email": "senellise@gmail.com"
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
$config = PhpCsFixer\Config::create()
|
||||
->setRiskyAllowed(true)
|
||||
->setRules([
|
||||
'@PSR2' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'declare_strict_types' => false,
|
||||
'concat_space' => ['spacing'=>'one'],
|
||||
// 'ordered_imports' => true,
|
||||
// 'phpdoc_align' => ['align'=>'vertical'],
|
||||
// 'native_function_invocation' => true,
|
||||
])
|
||||
->setFinder(
|
||||
PhpCsFixer\Finder::create()
|
||||
->in(__DIR__.'/src')
|
||||
->name('*.php')
|
||||
)
|
||||
;
|
||||
|
||||
return $config;
|
||||
@@ -1,5 +1,22 @@
|
||||
# Change Log
|
||||
|
||||
## 6.4.1 - 2019-10-23
|
||||
|
||||
* No `guzzle.phar` was created in 6.4.0 due expired API token. This release will fix that
|
||||
* Added `parent::__construct()` to `FileCookieJar` and `SessionCookieJar`
|
||||
|
||||
## 6.4.0 - 2019-10-23
|
||||
|
||||
* Improvement: Improved error messages when using curl < 7.21.2 [#2108](https://github.com/guzzle/guzzle/pull/2108)
|
||||
* Fix: Test if response is readable before returning a summary in `RequestException::getResponseBodySummary()` [#2081](https://github.com/guzzle/guzzle/pull/2081)
|
||||
* Fix: Add support for GUZZLE_CURL_SELECT_TIMEOUT environment variable [#2161](https://github.com/guzzle/guzzle/pull/2161)
|
||||
* Improvement: Added `GuzzleHttp\Exception\InvalidArgumentException` [#2163](https://github.com/guzzle/guzzle/pull/2163)
|
||||
* Improvement: Added `GuzzleHttp\_current_time()` to use `hrtime()` if that function exists. [#2242](https://github.com/guzzle/guzzle/pull/2242)
|
||||
* Improvement: Added curl's `appconnect_time` in `TransferStats` [#2284](https://github.com/guzzle/guzzle/pull/2284)
|
||||
* Improvement: Make GuzzleException extend Throwable wherever it's available [#2273](https://github.com/guzzle/guzzle/pull/2273)
|
||||
* Fix: Prevent concurrent writes to file when saving `CookieJar` [#2335](https://github.com/guzzle/guzzle/pull/2335)
|
||||
* Improvement: Update `MockHandler` so we can test transfer time [#2362](https://github.com/guzzle/guzzle/pull/2362)
|
||||
|
||||
## 6.3.3 - 2018-04-22
|
||||
|
||||
* Fix: Default headers when decode_content is specified
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
FROM composer:latest as setup
|
||||
|
||||
RUN mkdir /guzzle
|
||||
|
||||
WORKDIR /guzzle
|
||||
|
||||
RUN set -xe \
|
||||
&& composer init --name=guzzlehttp/test --description="Simple project for testing Guzzle scripts" --author="Márk Sági-Kazár <mark.sagikazar@gmail.com>" --no-interaction \
|
||||
&& composer require guzzlehttp/guzzle
|
||||
|
||||
|
||||
FROM php:7.3
|
||||
|
||||
RUN mkdir /guzzle
|
||||
|
||||
WORKDIR /guzzle
|
||||
|
||||
COPY --from=setup /guzzle /guzzle
|
||||
@@ -21,19 +21,18 @@ trivial to integrate with web services.
|
||||
|
||||
```php
|
||||
$client = new \GuzzleHttp\Client();
|
||||
$res = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
|
||||
echo $res->getStatusCode();
|
||||
// 200
|
||||
echo $res->getHeaderLine('content-type');
|
||||
// 'application/json; charset=utf8'
|
||||
echo $res->getBody();
|
||||
// '{"id": 1420053, "name": "guzzle", ...}'
|
||||
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle');
|
||||
|
||||
// Send an asynchronous request.
|
||||
echo $response->getStatusCode(); # 200
|
||||
echo $response->getHeaderLine('content-type'); # 'application/json; charset=utf8'
|
||||
echo $response->getBody(); # '{"id": 1420053, "name": "guzzle", ...}'
|
||||
|
||||
# Send an asynchronous request.
|
||||
$request = new \GuzzleHttp\Psr7\Request('GET', 'http://httpbin.org');
|
||||
$promise = $client->sendAsync($request)->then(function ($response) {
|
||||
echo 'I completed! ' . $response->getBody();
|
||||
});
|
||||
|
||||
$promise->wait();
|
||||
```
|
||||
|
||||
@@ -57,7 +56,7 @@ curl -sS https://getcomposer.org/installer | php
|
||||
Next, run the Composer command to install the latest stable version of Guzzle:
|
||||
|
||||
```bash
|
||||
php composer.phar require guzzlehttp/guzzle
|
||||
composer require guzzlehttp/guzzle
|
||||
```
|
||||
|
||||
After installing, you need to require Composer's autoloader:
|
||||
@@ -69,7 +68,7 @@ require 'vendor/autoload.php';
|
||||
You can then later update Guzzle using composer:
|
||||
|
||||
```bash
|
||||
composer.phar update
|
||||
composer update
|
||||
```
|
||||
|
||||
|
||||
@@ -86,6 +85,6 @@ composer.phar update
|
||||
[guzzle-4-repo]: https://github.com/guzzle/guzzle/tree/4.x
|
||||
[guzzle-5-repo]: https://github.com/guzzle/guzzle/tree/5.3
|
||||
[guzzle-6-repo]: https://github.com/guzzle/guzzle
|
||||
[guzzle-3-docs]: http://guzzle3.readthedocs.org/en/latest/
|
||||
[guzzle-3-docs]: http://guzzle3.readthedocs.org
|
||||
[guzzle-5-docs]: http://guzzle.readthedocs.org/en/5.3/
|
||||
[guzzle-6-docs]: http://guzzle.readthedocs.org/en/latest/
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
"name": "guzzlehttp/guzzle",
|
||||
"type": "library",
|
||||
"description": "Guzzle is a PHP HTTP client library",
|
||||
"keywords": ["framework", "http", "rest", "web service", "curl", "client", "HTTP client"],
|
||||
"keywords": [
|
||||
"framework",
|
||||
"http",
|
||||
"rest",
|
||||
"web service",
|
||||
"curl",
|
||||
"client",
|
||||
"HTTP client"
|
||||
],
|
||||
"homepage": "http://guzzlephp.org/",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
@@ -14,31 +22,37 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5",
|
||||
"guzzlehttp/psr7": "^1.4",
|
||||
"guzzlehttp/promises": "^1.0"
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.0",
|
||||
"guzzlehttp/psr7": "^1.6.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-curl": "*",
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
|
||||
"psr/log": "^1.0"
|
||||
},
|
||||
"autoload": {
|
||||
"files": ["src/functions_include.php"],
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Tests\\": "tests/"
|
||||
}
|
||||
"psr/log": "^1.1"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Required for using the Log middleware"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.3-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\": "src/"
|
||||
},
|
||||
"files": [
|
||||
"src/functions_include.php"
|
||||
]
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"GuzzleHttp\\Tests\\": "tests/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
parameters:
|
||||
level: 1
|
||||
paths:
|
||||
- src
|
||||
|
||||
ignoreErrors:
|
||||
-
|
||||
message: '#Function uri_template not found#'
|
||||
path: %currentWorkingDirectory%/src/functions.php
|
||||
@@ -210,7 +210,7 @@ class Client implements ClientInterface
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function prepareDefaults($options)
|
||||
private function prepareDefaults(array $options)
|
||||
{
|
||||
$defaults = $this->config;
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ use Psr\Http\Message\UriInterface;
|
||||
*/
|
||||
interface ClientInterface
|
||||
{
|
||||
const VERSION = '6.3.3';
|
||||
const VERSION = '6.4.1';
|
||||
|
||||
/**
|
||||
* Send an HTTP request.
|
||||
|
||||
@@ -120,7 +120,7 @@ class CookieJar implements CookieJarInterface
|
||||
} elseif (!$path) {
|
||||
$this->cookies = array_filter(
|
||||
$this->cookies,
|
||||
function (SetCookie $cookie) use ($path, $domain) {
|
||||
function (SetCookie $cookie) use ($domain) {
|
||||
return !$cookie->matchesDomain($domain);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ class FileCookieJar extends CookieJar
|
||||
*/
|
||||
public function __construct($cookieFile, $storeSessionCookies = false)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->filename = $cookieFile;
|
||||
$this->storeSessionCookies = $storeSessionCookies;
|
||||
|
||||
@@ -56,7 +57,7 @@ class FileCookieJar extends CookieJar
|
||||
}
|
||||
|
||||
$jsonStr = \GuzzleHttp\json_encode($json);
|
||||
if (false === file_put_contents($filename, $jsonStr)) {
|
||||
if (false === file_put_contents($filename, $jsonStr, LOCK_EX)) {
|
||||
throw new \RuntimeException("Unable to save file {$filename}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ class SessionCookieJar extends CookieJar
|
||||
*/
|
||||
public function __construct($sessionKey, $storeSessionCookies = false)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->sessionKey = $sessionKey;
|
||||
$this->storeSessionCookies = $storeSessionCookies;
|
||||
$this->load();
|
||||
|
||||
@@ -227,7 +227,7 @@ class SetCookie
|
||||
/**
|
||||
* Get whether or not this is a secure cookie
|
||||
*
|
||||
* @return null|bool
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getSecure()
|
||||
{
|
||||
@@ -247,7 +247,7 @@ class SetCookie
|
||||
/**
|
||||
* Get whether or not this is a session cookie
|
||||
*
|
||||
* @return null|bool
|
||||
* @return bool|null
|
||||
*/
|
||||
public function getDiscard()
|
||||
{
|
||||
|
||||
@@ -4,4 +4,6 @@ namespace GuzzleHttp\Exception;
|
||||
/**
|
||||
* Exception when a client error is encountered (4xx codes)
|
||||
*/
|
||||
class ClientException extends BadResponseException {}
|
||||
class ClientException extends BadResponseException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
/**
|
||||
* @method string getMessage()
|
||||
* @method \Throwable|null getPrevious()
|
||||
* @method mixed getCode()
|
||||
* @method string getFile()
|
||||
* @method int getLine()
|
||||
* @method array getTrace()
|
||||
* @method string getTraceAsString()
|
||||
*/
|
||||
interface GuzzleException {}
|
||||
use Throwable;
|
||||
|
||||
if (interface_exists(Throwable::class)) {
|
||||
interface GuzzleException extends Throwable
|
||||
{
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* @method string getMessage()
|
||||
* @method \Throwable|null getPrevious()
|
||||
* @method mixed getCode()
|
||||
* @method string getFile()
|
||||
* @method int getLine()
|
||||
* @method array getTrace()
|
||||
* @method string getTraceAsString()
|
||||
*/
|
||||
interface GuzzleException
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
final class InvalidArgumentException extends \InvalidArgumentException implements GuzzleException
|
||||
{
|
||||
}
|
||||
@@ -126,7 +126,7 @@ class RequestException extends TransferException
|
||||
{
|
||||
$body = $response->getBody();
|
||||
|
||||
if (!$body->isSeekable()) {
|
||||
if (!$body->isSeekable() || !$body->isReadable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,4 +4,6 @@ namespace GuzzleHttp\Exception;
|
||||
/**
|
||||
* Exception when a server error is encountered (5xx codes)
|
||||
*/
|
||||
class ServerException extends BadResponseException {}
|
||||
class ServerException extends BadResponseException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
class TooManyRedirectsException extends RequestException {}
|
||||
class TooManyRedirectsException extends RequestException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
namespace GuzzleHttp\Exception;
|
||||
|
||||
class TransferException extends \RuntimeException implements GuzzleException {}
|
||||
class TransferException extends \RuntimeException implements GuzzleException
|
||||
{
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ use Psr\Http\Message\RequestInterface;
|
||||
*/
|
||||
class CurlFactory implements CurlFactoryInterface
|
||||
{
|
||||
const CURL_VERSION_STR = 'curl_version';
|
||||
const LOW_CURL_VERSION_NUMBER = '7.21.2';
|
||||
|
||||
/** @var array */
|
||||
private $handles = [];
|
||||
|
||||
@@ -117,6 +120,7 @@ class CurlFactory implements CurlFactoryInterface
|
||||
private static function invokeStats(EasyHandle $easy)
|
||||
{
|
||||
$curlStats = curl_getinfo($easy->handle);
|
||||
$curlStats['appconnect_time'] = curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME);
|
||||
$stats = new TransferStats(
|
||||
$easy->request,
|
||||
$easy->response,
|
||||
@@ -136,7 +140,9 @@ class CurlFactory implements CurlFactoryInterface
|
||||
$ctx = [
|
||||
'errno' => $easy->errno,
|
||||
'error' => curl_error($easy->handle),
|
||||
'appconnect_time' => curl_getinfo($easy->handle, CURLINFO_APPCONNECT_TIME),
|
||||
] + curl_getinfo($easy->handle);
|
||||
$ctx[self::CURL_VERSION_STR] = curl_version()['version'];
|
||||
$factory->release($easy);
|
||||
|
||||
// Retry when nothing is present or when curl failed to rewind.
|
||||
@@ -172,13 +178,22 @@ class CurlFactory implements CurlFactoryInterface
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$message = sprintf(
|
||||
'cURL error %s: %s (%s)',
|
||||
$ctx['errno'],
|
||||
$ctx['error'],
|
||||
'see http://curl.haxx.se/libcurl/c/libcurl-errors.html'
|
||||
);
|
||||
if (version_compare($ctx[self::CURL_VERSION_STR], self::LOW_CURL_VERSION_NUMBER)) {
|
||||
$message = sprintf(
|
||||
'cURL error %s: %s (%s)',
|
||||
$ctx['errno'],
|
||||
$ctx['error'],
|
||||
'see https://curl.haxx.se/libcurl/c/libcurl-errors.html'
|
||||
);
|
||||
} else {
|
||||
$message = sprintf(
|
||||
'cURL error %s: %s (%s) for %s',
|
||||
$ctx['errno'],
|
||||
$ctx['error'],
|
||||
'see https://curl.haxx.se/libcurl/c/libcurl-errors.html',
|
||||
$easy->request->getUri()
|
||||
);
|
||||
}
|
||||
|
||||
// Create a connection exception if it was a specific error code.
|
||||
$error = isset($connectionErrors[$easy->errno])
|
||||
|
||||
@@ -37,8 +37,14 @@ class CurlMultiHandler
|
||||
{
|
||||
$this->factory = isset($options['handle_factory'])
|
||||
? $options['handle_factory'] : new CurlFactory(50);
|
||||
$this->selectTimeout = isset($options['select_timeout'])
|
||||
? $options['select_timeout'] : 1;
|
||||
|
||||
if (isset($options['select_timeout'])) {
|
||||
$this->selectTimeout = $options['select_timeout'];
|
||||
} elseif ($selectTimeout = getenv('GUZZLE_CURL_SELECT_TIMEOUT')) {
|
||||
$this->selectTimeout = $selectTimeout;
|
||||
} else {
|
||||
$this->selectTimeout = 1;
|
||||
}
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
@@ -82,7 +88,7 @@ class CurlMultiHandler
|
||||
{
|
||||
// Add any delayed handles if needed.
|
||||
if ($this->delays) {
|
||||
$currentTime = microtime(true);
|
||||
$currentTime = \GuzzleHttp\_current_time();
|
||||
foreach ($this->delays as $id => $delay) {
|
||||
if ($currentTime >= $delay) {
|
||||
unset($this->delays[$id]);
|
||||
@@ -134,7 +140,7 @@ class CurlMultiHandler
|
||||
if (empty($easy->options['delay'])) {
|
||||
curl_multi_add_handle($this->_mh, $easy->handle);
|
||||
} else {
|
||||
$this->delays[$id] = microtime(true) + ($easy->options['delay'] / 1000);
|
||||
$this->delays[$id] = \GuzzleHttp\_current_time() + ($easy->options['delay'] / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +192,7 @@ class CurlMultiHandler
|
||||
|
||||
private function timeToNext()
|
||||
{
|
||||
$currentTime = microtime(true);
|
||||
$currentTime = \GuzzleHttp\_current_time();
|
||||
$nextTime = PHP_INT_MAX;
|
||||
foreach ($this->delays as $time) {
|
||||
if ($time < $nextTime) {
|
||||
|
||||
@@ -182,7 +182,8 @@ class MockHandler implements \Countable
|
||||
$reason = null
|
||||
) {
|
||||
if (isset($options['on_stats'])) {
|
||||
$stats = new TransferStats($request, $response, 0, $reason);
|
||||
$transferTime = isset($options['transfer_time']) ? $options['transfer_time'] : 0;
|
||||
$stats = new TransferStats($request, $response, $transferTime, $reason);
|
||||
call_user_func($options['on_stats'], $stats);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class StreamHandler
|
||||
usleep($options['delay'] * 1000);
|
||||
}
|
||||
|
||||
$startTime = isset($options['on_stats']) ? microtime(true) : null;
|
||||
$startTime = isset($options['on_stats']) ? \GuzzleHttp\_current_time() : null;
|
||||
|
||||
try {
|
||||
// Does not support the expect header.
|
||||
@@ -42,7 +42,7 @@ class StreamHandler
|
||||
// Append a content-length header if body size is zero to match
|
||||
// cURL's behavior.
|
||||
if (0 === $request->getBody()->getSize()) {
|
||||
$request = $request->withHeader('Content-Length', 0);
|
||||
$request = $request->withHeader('Content-Length', '0');
|
||||
}
|
||||
|
||||
return $this->createResponse(
|
||||
@@ -82,7 +82,7 @@ class StreamHandler
|
||||
$stats = new TransferStats(
|
||||
$request,
|
||||
$response,
|
||||
microtime(true) - $startTime,
|
||||
\GuzzleHttp\_current_time() - $startTime,
|
||||
$error,
|
||||
[]
|
||||
);
|
||||
@@ -343,13 +343,25 @@ class StreamHandler
|
||||
if ('v4' === $options['force_ip_resolve']) {
|
||||
$records = dns_get_record($uri->getHost(), DNS_A);
|
||||
if (!isset($records[0]['ip'])) {
|
||||
throw new ConnectException(sprintf("Could not resolve IPv4 address for host '%s'", $uri->getHost()), $request);
|
||||
throw new ConnectException(
|
||||
sprintf(
|
||||
"Could not resolve IPv4 address for host '%s'",
|
||||
$uri->getHost()
|
||||
),
|
||||
$request
|
||||
);
|
||||
}
|
||||
$uri = $uri->withHost($records[0]['ip']);
|
||||
} elseif ('v6' === $options['force_ip_resolve']) {
|
||||
$records = dns_get_record($uri->getHost(), DNS_AAAA);
|
||||
if (!isset($records[0]['ipv6'])) {
|
||||
throw new ConnectException(sprintf("Could not resolve IPv6 address for host '%s'", $uri->getHost()), $request);
|
||||
throw new ConnectException(
|
||||
sprintf(
|
||||
"Could not resolve IPv6 address for host '%s'",
|
||||
$uri->getHost()
|
||||
),
|
||||
$request
|
||||
);
|
||||
}
|
||||
$uri = $uri->withHost('[' . $records[0]['ipv6'] . ']');
|
||||
}
|
||||
|
||||
@@ -206,7 +206,7 @@ class HandlerStack
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $name
|
||||
* @param string $name
|
||||
* @return int
|
||||
*/
|
||||
private function findByName($name)
|
||||
@@ -223,10 +223,10 @@ class HandlerStack
|
||||
/**
|
||||
* Splices a function into the middleware list at a specific position.
|
||||
*
|
||||
* @param $findName
|
||||
* @param $withName
|
||||
* @param string $findName
|
||||
* @param string $withName
|
||||
* @param callable $middleware
|
||||
* @param $before
|
||||
* @param bool $before
|
||||
*/
|
||||
private function splice($findName, $withName, callable $middleware, $before)
|
||||
{
|
||||
|
||||
@@ -7,7 +7,6 @@ use GuzzleHttp\Promise\RejectedPromise;
|
||||
use GuzzleHttp\Psr7;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* Functions used to create and wrap handlers with handler middleware.
|
||||
@@ -39,7 +38,7 @@ final class Middleware
|
||||
$cookieJar->extractCookies($request, $response);
|
||||
return $response;
|
||||
}
|
||||
);
|
||||
);
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -58,7 +57,7 @@ final class Middleware
|
||||
return $handler($request, $options);
|
||||
}
|
||||
return $handler($request, $options)->then(
|
||||
function (ResponseInterface $response) use ($request, $handler) {
|
||||
function (ResponseInterface $response) use ($request) {
|
||||
$code = $response->getStatusCode();
|
||||
if ($code < 400) {
|
||||
return $response;
|
||||
@@ -183,7 +182,7 @@ final class Middleware
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = LogLevel::INFO)
|
||||
public static function log(LoggerInterface $logger, MessageFormatter $formatter, $logLevel = 'info' /* \Psr\Log\LogLevel::INFO */)
|
||||
{
|
||||
return function (callable $handler) use ($logger, $formatter, $logLevel) {
|
||||
return function ($request, array $options) use ($handler, $logger, $formatter, $logLevel) {
|
||||
|
||||
@@ -6,7 +6,7 @@ use Psr\Http\Message\RequestInterface;
|
||||
use GuzzleHttp\Promise\EachPromise;
|
||||
|
||||
/**
|
||||
* Sends and iterator of requests concurrently using a capped pool size.
|
||||
* Sends an iterator of requests concurrently using a capped pool size.
|
||||
*
|
||||
* The pool will read from an iterator until it is cancelled or until the
|
||||
* iterator is consumed. When a request is yielded, the request is sent after
|
||||
|
||||
@@ -186,7 +186,7 @@ class RedirectMiddleware
|
||||
if ($options['allow_redirects']['referer']
|
||||
&& $modify['uri']->getScheme() === $request->getUri()->getScheme()
|
||||
) {
|
||||
$uri = $request->getUri()->withUserInfo('', '');
|
||||
$uri = $request->getUri()->withUserInfo('');
|
||||
$modify['set_headers']['Referer'] = (string) $uri;
|
||||
} else {
|
||||
$modify['remove_headers'][] = 'Referer';
|
||||
|
||||
@@ -22,7 +22,7 @@ final class RequestOptions
|
||||
* - strict: (bool, default=false) Set to true to use strict redirects
|
||||
* meaning redirect POST requests with POST requests vs. doing what most
|
||||
* browsers do which is redirect POST requests with GET requests
|
||||
* - referer: (bool, default=true) Set to false to disable the Referer
|
||||
* - referer: (bool, default=false) Set to true to enable the Referer
|
||||
* header.
|
||||
* - protocols: (array, default=['http', 'https']) Allowed redirect
|
||||
* protocols.
|
||||
|
||||
@@ -19,6 +19,9 @@ class RetryMiddleware
|
||||
/** @var callable */
|
||||
private $decider;
|
||||
|
||||
/** @var callable */
|
||||
private $delay;
|
||||
|
||||
/**
|
||||
* @param callable $decider Function that accepts the number of retries,
|
||||
* a request, [response], and [exception] and
|
||||
@@ -42,7 +45,7 @@ class RetryMiddleware
|
||||
/**
|
||||
* Default exponential backoff delay function.
|
||||
*
|
||||
* @param $retries
|
||||
* @param int $retries
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ final class TransferStats
|
||||
/**
|
||||
* @param RequestInterface $request Request that was sent.
|
||||
* @param ResponseInterface $response Response received (if any)
|
||||
* @param null $transferTime Total handler transfer time.
|
||||
* @param float|null $transferTime Total handler transfer time.
|
||||
* @param mixed $handlerErrorData Handler error data.
|
||||
* @param array $handlerStats Handler specific stats.
|
||||
*/
|
||||
|
||||
@@ -196,7 +196,8 @@ function default_ca_bundle()
|
||||
}
|
||||
}
|
||||
|
||||
throw new \RuntimeException(<<< EOT
|
||||
throw new \RuntimeException(
|
||||
<<< EOT
|
||||
No system CA bundle could be found in any of the the common system locations.
|
||||
PHP versions earlier than 5.6 are not properly configured to use the system's
|
||||
CA bundle by default. In order to verify peer certificates, you will need to
|
||||
@@ -294,14 +295,14 @@ function is_host_in_noproxy($host, array $noProxyArray)
|
||||
* @param int $options Bitmask of JSON decode options.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \InvalidArgumentException if the JSON cannot be decoded.
|
||||
* @throws Exception\InvalidArgumentException if the JSON cannot be decoded.
|
||||
* @link http://www.php.net/manual/en/function.json-decode.php
|
||||
*/
|
||||
function json_decode($json, $assoc = false, $depth = 512, $options = 0)
|
||||
{
|
||||
$data = \json_decode($json, $assoc, $depth, $options);
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new \InvalidArgumentException(
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'json_decode error: ' . json_last_error_msg()
|
||||
);
|
||||
}
|
||||
@@ -317,17 +318,29 @@ function json_decode($json, $assoc = false, $depth = 512, $options = 0)
|
||||
* @param int $depth Set the maximum depth. Must be greater than zero.
|
||||
*
|
||||
* @return string
|
||||
* @throws \InvalidArgumentException if the JSON cannot be encoded.
|
||||
* @throws Exception\InvalidArgumentException if the JSON cannot be encoded.
|
||||
* @link http://www.php.net/manual/en/function.json-encode.php
|
||||
*/
|
||||
function json_encode($value, $options = 0, $depth = 512)
|
||||
{
|
||||
$json = \json_encode($value, $options, $depth);
|
||||
if (JSON_ERROR_NONE !== json_last_error()) {
|
||||
throw new \InvalidArgumentException(
|
||||
throw new Exception\InvalidArgumentException(
|
||||
'json_encode error: ' . json_last_error_msg()
|
||||
);
|
||||
}
|
||||
|
||||
return $json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for the hrtime() or microtime() functions
|
||||
* (depending on the PHP version, one of the two is used)
|
||||
*
|
||||
* @return float|mixed UNIX timestamp
|
||||
* @internal
|
||||
*/
|
||||
function _current_time()
|
||||
{
|
||||
return function_exists('hrtime') ? hrtime(true) / 1e9 : microtime(true);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/vendor
|
||||
composer.phar
|
||||
composer.lock
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
/.Trash-1000
|
||||
.idea/
|
||||
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Nicolas Tallefourtane dev@nicolab.net
|
||||
|
||||
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.
|
||||
@@ -0,0 +1,249 @@
|
||||
# nicolab/php-ftp-client
|
||||
|
||||
A flexible FTP and SSL-FTP client for PHP.
|
||||
This lib provides helpers easy to use to manage the remote files.
|
||||
|
||||
> This package is aimed to remain simple and light. It's only a wrapper of the FTP native API of PHP, with some useful helpers. If you want to customize some methods, you can do this by inheriting one of the [3 classes of the package](src/FtpClient).
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
* Use composer: _require_ `nicolab/php-ftp-client`
|
||||
|
||||
* Or use GIT clone command: `git clone git@github.com:Nicolab/php-ftp-client.git`
|
||||
|
||||
* Or download the library, configure your autoloader or include the 3 files of `php-ftp-client/src/FtpClient` directory.
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
Connect to a server FTP :
|
||||
|
||||
```php
|
||||
$ftp = new \FtpClient\FtpClient();
|
||||
$ftp->connect($host);
|
||||
$ftp->login($login, $password);
|
||||
```
|
||||
|
||||
OR
|
||||
|
||||
Connect to a server FTP via SSL (on port 990 or another port) :
|
||||
|
||||
```php
|
||||
$ftp = new \FtpClient\FtpClient();
|
||||
$ftp->connect($host, true, 990);
|
||||
$ftp->login($login, $password);
|
||||
```
|
||||
|
||||
Note: The connection is implicitly closed at the end of script execution (when the object is destroyed). Therefore it is unnecessary to call `$ftp->close()`, except for an explicit re-connection.
|
||||
|
||||
|
||||
### Usage
|
||||
|
||||
Upload all files and all directories is easy :
|
||||
|
||||
```php
|
||||
// upload with the BINARY mode
|
||||
$ftp->putAll($source_directory, $target_directory);
|
||||
|
||||
// Is equal to
|
||||
$ftp->putAll($source_directory, $target_directory, FTP_BINARY);
|
||||
|
||||
// or upload with the ASCII mode
|
||||
$ftp->putAll($source_directory, $target_directory, FTP_ASCII);
|
||||
```
|
||||
|
||||
*Note : FTP_ASCII and FTP_BINARY are predefined PHP internal constants.*
|
||||
|
||||
Get a directory size :
|
||||
|
||||
```php
|
||||
// size of the current directory
|
||||
$size = $ftp->dirSize();
|
||||
|
||||
// size of a given directory
|
||||
$size = $ftp->dirSize('/path/of/directory');
|
||||
```
|
||||
|
||||
Count the items in a directory :
|
||||
|
||||
```php
|
||||
// count in the current directory
|
||||
$total = $ftp->count();
|
||||
|
||||
// count in a given directory
|
||||
$total = $ftp->count('/path/of/directory');
|
||||
|
||||
// count only the "files" in the current directory
|
||||
$total_file = $ftp->count('.', 'file');
|
||||
|
||||
// count only the "files" in a given directory
|
||||
$total_file = $ftp->count('/path/of/directory', 'file');
|
||||
|
||||
// count only the "directories" in a given directory
|
||||
$total_dir = $ftp->count('/path/of/directory', 'directory');
|
||||
|
||||
// count only the "symbolic links" in a given directory
|
||||
$total_link = $ftp->count('/path/of/directory', 'link');
|
||||
```
|
||||
|
||||
Detailed list of all files and directories :
|
||||
|
||||
```php
|
||||
// scan the current directory and returns the details of each item
|
||||
$items = $ftp->scanDir();
|
||||
|
||||
// scan the current directory (recursive) and returns the details of each item
|
||||
var_dump($ftp->scanDir('.', true));
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
'directory#www' =>
|
||||
array (size=10)
|
||||
'permissions' => string 'drwx---r-x' (length=10)
|
||||
'number' => string '3' (length=1)
|
||||
'owner' => string '32385' (length=5)
|
||||
'group' => string 'users' (length=5)
|
||||
'size' => string '5' (length=1)
|
||||
'month' => string 'Nov' (length=3)
|
||||
'day' => string '24' (length=2)
|
||||
'time' => string '17:25' (length=5)
|
||||
'name' => string 'www' (length=3)
|
||||
'type' => string 'directory' (length=9)
|
||||
|
||||
'link#www/index.html' =>
|
||||
array (size=11)
|
||||
'permissions' => string 'lrwxrwxrwx' (length=10)
|
||||
'number' => string '1' (length=1)
|
||||
'owner' => string '0' (length=1)
|
||||
'group' => string 'users' (length=5)
|
||||
'size' => string '38' (length=2)
|
||||
'month' => string 'Nov' (length=3)
|
||||
'day' => string '16' (length=2)
|
||||
'time' => string '14:57' (length=5)
|
||||
'name' => string 'index.html' (length=10)
|
||||
'type' => string 'link' (length=4)
|
||||
'target' => string '/var/www/shared/index.html' (length=26)
|
||||
|
||||
'file#www/README' =>
|
||||
array (size=10)
|
||||
'permissions' => string '-rw----r--' (length=10)
|
||||
'number' => string '1' (length=1)
|
||||
'owner' => string '32385' (length=5)
|
||||
'group' => string 'users' (length=5)
|
||||
'size' => string '0' (length=1)
|
||||
'month' => string 'Nov' (length=3)
|
||||
'day' => string '24' (length=2)
|
||||
'time' => string '17:25' (length=5)
|
||||
'name' => string 'README' (length=6)
|
||||
'type' => string 'file' (length=4)
|
||||
|
||||
|
||||
All FTP PHP functions are supported and some improved :
|
||||
|
||||
```php
|
||||
// Requests execution of a command on the FTP server
|
||||
$ftp->exec($command);
|
||||
|
||||
// Turns passive mode on or off
|
||||
$ftp->pasv(true);
|
||||
|
||||
// Set permissions on a file via FTP
|
||||
$ftp->chmod(0777, 'file.php');
|
||||
|
||||
// Removes a directory
|
||||
$ftp->rmdir('path/of/directory/to/remove');
|
||||
|
||||
// Removes a directory (recursive)
|
||||
$ftp->rmdir('path/of/directory/to/remove', true);
|
||||
|
||||
// Creates a directory
|
||||
$ftp->mkdir('path/of/directory/to/create');
|
||||
|
||||
// Creates a directory (recursive),
|
||||
// creates automaticaly the sub directory if not exist
|
||||
$ftp->mkdir('path/of/directory/to/create', true);
|
||||
|
||||
// and more ...
|
||||
```
|
||||
|
||||
Get the help information of remote FTP server :
|
||||
|
||||
```php
|
||||
var_dump($ftp->help());
|
||||
```
|
||||
|
||||
Result :
|
||||
|
||||
array (size=6)
|
||||
0 => string '214-The following SITE commands are recognized' (length=46)
|
||||
1 => string ' ALIAS' (length=6)
|
||||
2 => string ' CHMOD' (length=6)
|
||||
3 => string ' IDLE' (length=5)
|
||||
4 => string ' UTIME' (length=6)
|
||||
5 => string '214 Pure-FTPd - http://pureftpd.org/' (length=36)
|
||||
|
||||
|
||||
_Note : The result depend of FTP server._
|
||||
|
||||
|
||||
### Extend
|
||||
|
||||
Create your custom `FtpClient`.
|
||||
|
||||
```php
|
||||
// MyFtpClient.php
|
||||
|
||||
/**
|
||||
* My custom FTP Client
|
||||
* @inheritDoc
|
||||
*/
|
||||
class MyFtpClient extends \FtpClient\FtpClient {
|
||||
|
||||
public function removeByTime($path, $timestamp) {
|
||||
// your code here
|
||||
}
|
||||
|
||||
public function search($regex) {
|
||||
// your code here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```php
|
||||
// example.php
|
||||
$ftp = new MyFtpClient();
|
||||
$ftp->connect($host);
|
||||
$ftp->login($login, $password);
|
||||
|
||||
// remove the old files
|
||||
$ftp->removeByTime('/www/mysite.com/demo', time() - 86400));
|
||||
|
||||
// search PNG files
|
||||
$ftp->search('/(.*)\.png$/i');
|
||||
```
|
||||
|
||||
|
||||
## API doc
|
||||
|
||||
See the [source code](https://github.com/Nicolab/php-ftp-client/tree/master/src/FtpClient) for more details.
|
||||
It is fully documented :blue_book:
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
Tested with "atoum" unit testing framework.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/Nicolab/php-ftp-client/blob/master/LICENSE) c) 2014, Nicolas Tallefourtane.
|
||||
|
||||
|
||||
## Author
|
||||
|
||||
| [](http://nicolab.net) |
|
||||
|---|
|
||||
| [Nicolas Talle](http://nicolab.net) |
|
||||
| [](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=PGRH4ZXP36GUC) |
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "nicolab/php-ftp-client",
|
||||
"type": "library",
|
||||
"description": "A flexible FTP and SSL-FTP client for PHP. This lib provides helpers easy to use to manage the remote files.",
|
||||
"license": "MIT",
|
||||
"keywords": ["ftp", "sftp", "ssl-ftp", "ssl", "file", "server", "lib", "helper"],
|
||||
"homepage": "https://github.com/Nicolab/php-ftp-client",
|
||||
|
||||
"authors" : [
|
||||
{
|
||||
"name" : "Nicolas Tallefourtane",
|
||||
"email" : "dev@nicolab.net",
|
||||
"homepage" : "http://nicolab.net"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.4",
|
||||
"ext-ftp": "*"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-0": {"FtpClient": "src/"}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,938 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of the `nicolab/php-ftp-client` package.
|
||||
*
|
||||
* (c) Nicolas Tallefourtane <dev@nicolab.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @copyright Nicolas Tallefourtane http://nicolab.net
|
||||
*/
|
||||
namespace FtpClient;
|
||||
|
||||
use \Countable;
|
||||
|
||||
/**
|
||||
* The FTP and SSL-FTP client for PHP.
|
||||
*
|
||||
* @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded
|
||||
* @method bool cdup() cdup() Changes to the parent directory
|
||||
* @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server
|
||||
* @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP
|
||||
* @method bool delete() delete(string $path) Deletes a file on the FTP server
|
||||
* @method bool exec() exec(string $command) Requests execution of a command on the FTP server
|
||||
* @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file
|
||||
* @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server
|
||||
* @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream
|
||||
* @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server
|
||||
* @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file
|
||||
* @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking)
|
||||
* @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking)
|
||||
* @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking)
|
||||
* @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking)
|
||||
* @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking)
|
||||
* @method bool pasv() pasv(bool $pasv) Turns passive mode on or off
|
||||
* @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server
|
||||
* @method string pwd() pwd() Returns the current directory name
|
||||
* @method bool quit() quit() Closes an FTP connection
|
||||
* @method array raw() raw(string $command) Sends an arbitrary command to an FTP server
|
||||
* @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server
|
||||
* @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options
|
||||
* @method bool site() site(string $command) Sends a SITE command to the server
|
||||
* @method int size() size(string $remote_file) Returns the size of the given file
|
||||
* @method string systype() systype() Returns the system type identifier of the remote FTP server
|
||||
*
|
||||
* @author Nicolas Tallefourtane <dev@nicolab.net>
|
||||
*/
|
||||
class FtpClient implements Countable
|
||||
{
|
||||
/**
|
||||
* The connection with the server.
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $conn;
|
||||
|
||||
/**
|
||||
* PHP FTP functions wrapper.
|
||||
*
|
||||
* @var FtpWrapper
|
||||
*/
|
||||
private $ftp;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param resource|null $connection
|
||||
* @throws FtpException If FTP extension is not loaded.
|
||||
*/
|
||||
public function __construct($connection = null)
|
||||
{
|
||||
if (!extension_loaded('ftp')) {
|
||||
throw new FtpException('FTP extension is not loaded!');
|
||||
}
|
||||
|
||||
if ($connection) {
|
||||
$this->conn = $connection;
|
||||
}
|
||||
|
||||
$this->setWrapper(new FtpWrapper($this->conn));
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the connection when the object is destroyed.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if ($this->conn) {
|
||||
$this->ftp->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Call an internal method or a FTP method handled by the wrapper.
|
||||
*
|
||||
* Wrap the FTP PHP functions to call as method of FtpClient object.
|
||||
* The connection is automaticaly passed to the FTP PHP functions.
|
||||
*
|
||||
* @param string $method
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
* @throws FtpException When the function is not valid
|
||||
*/
|
||||
public function __call($method, array $arguments)
|
||||
{
|
||||
return $this->ftp->__call($method, $arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites the PHP limit
|
||||
*
|
||||
* @param string|null $memory The memory limit, if null is not modified
|
||||
* @param int $time_limit The max execution time, unlimited by default
|
||||
* @param bool $ignore_user_abort Ignore user abort, true by default
|
||||
* @return FtpClient
|
||||
*/
|
||||
public function setPhpLimit($memory = null, $time_limit = 0, $ignore_user_abort = true)
|
||||
{
|
||||
if (null !== $memory) {
|
||||
ini_set('memory_limit', $memory);
|
||||
}
|
||||
|
||||
ignore_user_abort($ignore_user_abort);
|
||||
set_time_limit($time_limit);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the help information of the remote FTP server.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function help()
|
||||
{
|
||||
return $this->ftp->raw('help');
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a FTP connection.
|
||||
*
|
||||
* @param string $host
|
||||
* @param bool $ssl
|
||||
* @param int $port
|
||||
* @param int $timeout
|
||||
*
|
||||
* @return FtpClient
|
||||
* @throws FtpException If unable to connect
|
||||
*/
|
||||
public function connect($host, $ssl = false, $port = 21, $timeout = 90)
|
||||
{
|
||||
if ($ssl) {
|
||||
$this->conn = $this->ftp->ssl_connect($host, $port, $timeout);
|
||||
} else {
|
||||
$this->conn = $this->ftp->connect($host, $port, $timeout);
|
||||
}
|
||||
|
||||
if (!$this->conn) {
|
||||
throw new FtpException('Unable to connect');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the current FTP connection.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
if ($this->conn) {
|
||||
$this->ftp->close();
|
||||
$this->conn = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection with the server.
|
||||
*
|
||||
* @return resource
|
||||
*/
|
||||
public function getConnection()
|
||||
{
|
||||
return $this->conn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the wrapper.
|
||||
*
|
||||
* @return FtpWrapper
|
||||
*/
|
||||
public function getWrapper()
|
||||
{
|
||||
return $this->ftp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs in to an FTP connection.
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
*
|
||||
* @return FtpClient
|
||||
* @throws FtpException If the login is incorrect
|
||||
*/
|
||||
public function login($username = 'anonymous', $password = '')
|
||||
{
|
||||
$result = $this->ftp->login($username, $password);
|
||||
|
||||
if ($result === false) {
|
||||
throw new FtpException('Login incorrect');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last modified time of the given file.
|
||||
* Return -1 on error
|
||||
*
|
||||
* @param string $remoteFile
|
||||
* @param string|null $format
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function modifiedTime($remoteFile, $format = null)
|
||||
{
|
||||
$time = $this->ftp->mdtm($remoteFile);
|
||||
|
||||
if ($time !== -1 && $format !== null) {
|
||||
return date($format, $time);
|
||||
}
|
||||
|
||||
return $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes to the parent directory.
|
||||
*
|
||||
* @throws FtpException
|
||||
* @return FtpClient
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$result = $this->ftp->cdup();
|
||||
|
||||
if ($result === false) {
|
||||
throw new FtpException('Unable to get parent folder');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of files in the given directory.
|
||||
*
|
||||
* @param string $directory The directory, by default is "." the current directory
|
||||
* @param bool $recursive
|
||||
* @param callable $filter A callable to filter the result, by default is asort() PHP function.
|
||||
* The result is passed in array argument,
|
||||
* must take the argument by reference !
|
||||
* The callable should proceed with the reference array
|
||||
* because is the behavior of several PHP sorting
|
||||
* functions (by reference ensure directly the compatibility
|
||||
* with all PHP sorting functions).
|
||||
*
|
||||
* @return array
|
||||
* @throws FtpException If unable to list the directory
|
||||
*/
|
||||
public function nlist($directory = '.', $recursive = false, $filter = 'sort')
|
||||
{
|
||||
if (!$this->isDir($directory)) {
|
||||
throw new FtpException('"'.$directory.'" is not a directory');
|
||||
}
|
||||
|
||||
$files = $this->ftp->nlist($directory);
|
||||
|
||||
if ($files === false) {
|
||||
throw new FtpException('Unable to list directory');
|
||||
}
|
||||
|
||||
$result = array();
|
||||
$dir_len = strlen($directory);
|
||||
|
||||
// if it's the current
|
||||
if (false !== ($kdot = array_search('.', $files))) {
|
||||
unset($files[$kdot]);
|
||||
}
|
||||
|
||||
// if it's the parent
|
||||
if(false !== ($kdot = array_search('..', $files))) {
|
||||
unset($files[$kdot]);
|
||||
}
|
||||
|
||||
if (!$recursive) {
|
||||
$result = $files;
|
||||
|
||||
// working with the reference (behavior of several PHP sorting functions)
|
||||
$filter($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// utils for recursion
|
||||
$flatten = function (array $arr) use (&$flatten) {
|
||||
$flat = [];
|
||||
|
||||
foreach ($arr as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
$flat = array_merge($flat, $flatten($v));
|
||||
} else {
|
||||
$flat[] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
return $flat;
|
||||
};
|
||||
|
||||
foreach ($files as $file) {
|
||||
$file = $directory.'/'.$file;
|
||||
|
||||
// if contains the root path (behavior of the recursivity)
|
||||
if (0 === strpos($file, $directory, $dir_len)) {
|
||||
$file = substr($file, $dir_len);
|
||||
}
|
||||
|
||||
if ($this->isDir($file)) {
|
||||
$result[] = $file;
|
||||
$items = $flatten($this->nlist($file, true, $filter));
|
||||
|
||||
foreach ($items as $item) {
|
||||
$result[] = $item;
|
||||
}
|
||||
|
||||
} else {
|
||||
$result[] = $file;
|
||||
}
|
||||
}
|
||||
|
||||
$result = array_unique($result);
|
||||
$filter($result);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory.
|
||||
*
|
||||
* @see FtpClient::rmdir()
|
||||
* @see FtpClient::remove()
|
||||
* @see FtpClient::put()
|
||||
* @see FtpClient::putAll()
|
||||
*
|
||||
* @param string $directory The directory
|
||||
* @param bool $recursive
|
||||
* @return array
|
||||
*/
|
||||
public function mkdir($directory, $recursive = false)
|
||||
{
|
||||
if (!$recursive or $this->isDir($directory)) {
|
||||
return $this->ftp->mkdir($directory);
|
||||
}
|
||||
|
||||
$result = false;
|
||||
$pwd = $this->ftp->pwd();
|
||||
$parts = explode('/', $directory);
|
||||
|
||||
foreach ($parts as $part) {
|
||||
if ($part == '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!@$this->ftp->chdir($part)) {
|
||||
$result = $this->ftp->mkdir($part);
|
||||
$this->ftp->chdir($part);
|
||||
}
|
||||
}
|
||||
|
||||
$this->ftp->chdir($pwd);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a directory.
|
||||
*
|
||||
* @see FtpClient::mkdir()
|
||||
* @see FtpClient::cleanDir()
|
||||
* @see FtpClient::remove()
|
||||
* @see FtpClient::delete()
|
||||
* @param string $directory
|
||||
* @param bool $recursive Forces deletion if the directory is not empty
|
||||
* @return bool
|
||||
* @throws FtpException If unable to list the directory to remove
|
||||
*/
|
||||
public function rmdir($directory, $recursive = true)
|
||||
{
|
||||
if ($recursive) {
|
||||
$files = $this->nlist($directory, false, 'rsort');
|
||||
|
||||
// remove children
|
||||
foreach ($files as $file) {
|
||||
$this->remove($file, true);
|
||||
}
|
||||
}
|
||||
|
||||
// remove the directory
|
||||
return $this->ftp->rmdir($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty directory.
|
||||
*
|
||||
* @see FtpClient::remove()
|
||||
* @see FtpClient::delete()
|
||||
* @see FtpClient::rmdir()
|
||||
*
|
||||
* @param string $directory
|
||||
* @return bool
|
||||
*/
|
||||
public function cleanDir($directory)
|
||||
{
|
||||
if (!$files = $this->nlist($directory)) {
|
||||
return $this->isEmpty($directory);
|
||||
}
|
||||
|
||||
// remove children
|
||||
foreach ($files as $file) {
|
||||
$this->remove($file, true);
|
||||
}
|
||||
|
||||
return $this->isEmpty($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a file or a directory.
|
||||
*
|
||||
* @see FtpClient::rmdir()
|
||||
* @see FtpClient::cleanDir()
|
||||
* @see FtpClient::delete()
|
||||
* @param string $path The path of the file or directory to remove
|
||||
* @param bool $recursive Is effective only if $path is a directory, {@see FtpClient::rmdir()}
|
||||
* @return bool
|
||||
*/
|
||||
public function remove($path, $recursive = false)
|
||||
{
|
||||
try {
|
||||
if (@$this->ftp->delete($path)
|
||||
or ($this->isDir($path) and $this->rmdir($path, $recursive))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directory exist.
|
||||
*
|
||||
* @param string $directory
|
||||
* @return bool
|
||||
* @throws FtpException
|
||||
*/
|
||||
public function isDir($directory)
|
||||
{
|
||||
$pwd = $this->ftp->pwd();
|
||||
|
||||
if ($pwd === false) {
|
||||
throw new FtpException('Unable to resolve the current directory');
|
||||
}
|
||||
|
||||
if (@$this->ftp->chdir($directory)) {
|
||||
$this->ftp->chdir($pwd);
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->ftp->chdir($pwd);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directory is empty.
|
||||
*
|
||||
* @param string $directory
|
||||
* @return bool
|
||||
*/
|
||||
public function isEmpty($directory)
|
||||
{
|
||||
return $this->count($directory, null, false) === 0 ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan a directory and returns the details of each item.
|
||||
*
|
||||
* @see FtpClient::nlist()
|
||||
* @see FtpClient::rawlist()
|
||||
* @see FtpClient::parseRawList()
|
||||
* @see FtpClient::dirSize()
|
||||
* @param string $directory
|
||||
* @param bool $recursive
|
||||
* @return array
|
||||
*/
|
||||
public function scanDir($directory = '.', $recursive = false)
|
||||
{
|
||||
return $this->parseRawList($this->rawlist($directory, $recursive));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total size of the given directory in bytes.
|
||||
*
|
||||
* @param string $directory The directory, by default is the current directory.
|
||||
* @param bool $recursive true by default
|
||||
* @return int The size in bytes.
|
||||
*/
|
||||
public function dirSize($directory = '.', $recursive = true)
|
||||
{
|
||||
$items = $this->scanDir($directory, $recursive);
|
||||
$size = 0;
|
||||
|
||||
foreach ($items as $item) {
|
||||
$size += (int) $item['size'];
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the items (file, directory, link, unknown).
|
||||
*
|
||||
* @param string $directory The directory, by default is the current directory.
|
||||
* @param string|null $type The type of item to count (file, directory, link, unknown)
|
||||
* @param bool $recursive true by default
|
||||
* @return int
|
||||
*/
|
||||
public function count($directory = '.', $type = null, $recursive = true)
|
||||
{
|
||||
$items = (null === $type ? $this->nlist($directory, $recursive)
|
||||
: $this->scanDir($directory, $recursive));
|
||||
|
||||
$count = 0;
|
||||
foreach ($items as $item) {
|
||||
if (null === $type or $item['type'] == $type) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a file from the FTP server into a string
|
||||
*
|
||||
* @param string $remote_file
|
||||
* @param int $mode
|
||||
* @param int $resumepos
|
||||
* @return string|null
|
||||
*/
|
||||
public function getContent($remote_file, $mode = FTP_BINARY, $resumepos = 0)
|
||||
{
|
||||
$handle = fopen('php://temp', 'r+');
|
||||
|
||||
if ($this->fget($handle, $remote_file, $mode, $resumepos)) {
|
||||
rewind($handle);
|
||||
return stream_get_contents($handle);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file to the server from a string.
|
||||
*
|
||||
* @param string $remote_file
|
||||
* @param string $content
|
||||
* @return FtpClient
|
||||
* @throws FtpException When the transfer fails
|
||||
*/
|
||||
public function putFromString($remote_file, $content)
|
||||
{
|
||||
$handle = fopen('php://temp', 'w');
|
||||
|
||||
fwrite($handle, $content);
|
||||
rewind($handle);
|
||||
|
||||
if ($this->ftp->fput($remote_file, $handle, FTP_BINARY)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new FtpException('Unable to put the file "'.$remote_file.'"');
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a file to the server.
|
||||
*
|
||||
* @param string $local_file
|
||||
* @return FtpClient
|
||||
* @throws FtpException When the transfer fails
|
||||
*/
|
||||
public function putFromPath($local_file)
|
||||
{
|
||||
$remote_file = basename($local_file);
|
||||
$handle = fopen($local_file, 'r');
|
||||
|
||||
if ($this->ftp->fput($remote_file, $handle, FTP_BINARY)) {
|
||||
rewind($handle);
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new FtpException(
|
||||
'Unable to put the remote file from the local file "'.$local_file.'"'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload files.
|
||||
*
|
||||
* @param string $source_directory
|
||||
* @param string $target_directory
|
||||
* @param int $mode
|
||||
* @return FtpClient
|
||||
*/
|
||||
public function putAll($source_directory, $target_directory, $mode = FTP_BINARY)
|
||||
{
|
||||
$d = dir($source_directory);
|
||||
|
||||
// do this for each file in the directory
|
||||
while ($file = $d->read()) {
|
||||
|
||||
// to prevent an infinite loop
|
||||
if ($file != "." && $file != "..") {
|
||||
|
||||
// do the following if it is a directory
|
||||
if (is_dir($source_directory.'/'.$file)) {
|
||||
|
||||
if (!$this->isDir($target_directory.'/'.$file)) {
|
||||
|
||||
// create directories that do not yet exist
|
||||
$this->ftp->mkdir($target_directory.'/'.$file);
|
||||
}
|
||||
|
||||
// recursive part
|
||||
$this->putAll(
|
||||
$source_directory.'/'.$file, $target_directory.'/'.$file,
|
||||
$mode
|
||||
);
|
||||
} else {
|
||||
|
||||
// put the files
|
||||
$this->ftp->put(
|
||||
$target_directory.'/'.$file, $source_directory.'/'.$file,
|
||||
$mode
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads all files from remote FTP directory
|
||||
*
|
||||
* @param string $source_directory The remote directory
|
||||
* @param string $target_directory The local directory
|
||||
* @param int $mode
|
||||
* @return FtpClient
|
||||
*/
|
||||
public function getAll($source_directory, $target_directory, $mode = FTP_BINARY)
|
||||
{
|
||||
if ($source_directory != ".") {
|
||||
if ($this->ftp->chdir($source_directory) == false) {
|
||||
throw new FtpException("Unable to change directory: ".$source_directory);
|
||||
}
|
||||
|
||||
if (!(is_dir($source_directory))) {
|
||||
mkdir($source_directory);
|
||||
}
|
||||
|
||||
chdir($source_directory);
|
||||
}
|
||||
|
||||
$contents = $this->ftp->nlist(".");
|
||||
|
||||
foreach ($contents as $file) {
|
||||
if ($file == '.' || $file == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->ftp->get($target_directory."/".$file, $file, $mode);
|
||||
}
|
||||
|
||||
$this->ftp->chdir("..");
|
||||
chdir("..");
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a detailed list of files in the given directory.
|
||||
*
|
||||
* @see FtpClient::nlist()
|
||||
* @see FtpClient::scanDir()
|
||||
* @see FtpClient::dirSize()
|
||||
* @param string $directory The directory, by default is the current directory
|
||||
* @param bool $recursive
|
||||
* @return array
|
||||
* @throws FtpException
|
||||
*/
|
||||
public function rawlist($directory = '.', $recursive = false)
|
||||
{
|
||||
if (!$this->isDir($directory)) {
|
||||
throw new FtpException('"'.$directory.'" is not a directory.');
|
||||
}
|
||||
|
||||
if (strpos($directory, " ") > 0) {
|
||||
$ftproot = $this->ftp->pwd();
|
||||
$this->ftp->chdir($directory);
|
||||
$list = $this->ftp->rawlist("");
|
||||
$this->ftp->chdir($ftproot);
|
||||
} else {
|
||||
$list = $this->ftp->rawlist($directory);
|
||||
}
|
||||
|
||||
$items = array();
|
||||
|
||||
if (!$list) {
|
||||
return $items;
|
||||
}
|
||||
|
||||
if (false == $recursive) {
|
||||
foreach ($list as $path => $item) {
|
||||
$chunks = preg_split("/\s+/", $item);
|
||||
|
||||
// if not "name"
|
||||
if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $directory.'/'.$chunks[8];
|
||||
|
||||
if (isset($chunks[9])) {
|
||||
$nbChunks = count($chunks);
|
||||
|
||||
for ($i = 9; $i < $nbChunks; $i++) {
|
||||
$path .= ' '.$chunks[$i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (substr($path, 0, 2) == './') {
|
||||
$path = substr($path, 2);
|
||||
}
|
||||
|
||||
$items[ $this->rawToType($item).'#'.$path ] = $item;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
$path = '';
|
||||
|
||||
foreach ($list as $item) {
|
||||
$len = strlen($item);
|
||||
|
||||
if (!$len
|
||||
|
||||
// "."
|
||||
|| ($item[$len-1] == '.' && $item[$len-2] == ' '
|
||||
|
||||
// ".."
|
||||
or $item[$len-1] == '.' && $item[$len-2] == '.' && $item[$len-3] == ' ')
|
||||
) {
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$chunks = preg_split("/\s+/", $item);
|
||||
|
||||
// if not "name"
|
||||
if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = $directory.'/'.$chunks[8];
|
||||
|
||||
if (isset($chunks[9])) {
|
||||
$nbChunks = count($chunks);
|
||||
|
||||
for ($i = 9; $i < $nbChunks; $i++) {
|
||||
$path .= ' '.$chunks[$i];
|
||||
}
|
||||
}
|
||||
|
||||
if (substr($path, 0, 2) == './') {
|
||||
$path = substr($path, 2);
|
||||
}
|
||||
|
||||
$items[$this->rawToType($item).'#'.$path] = $item;
|
||||
|
||||
if ($item[0] == 'd') {
|
||||
$sublist = $this->rawlist($path, true);
|
||||
|
||||
foreach ($sublist as $subpath => $subitem) {
|
||||
$items[$subpath] = $subitem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse raw list.
|
||||
*
|
||||
* @see FtpClient::rawlist()
|
||||
* @see FtpClient::scanDir()
|
||||
* @see FtpClient::dirSize()
|
||||
* @param array $rawlist
|
||||
* @return array
|
||||
*/
|
||||
public function parseRawList(array $rawlist)
|
||||
{
|
||||
$items = array();
|
||||
$path = '';
|
||||
|
||||
foreach ($rawlist as $key => $child) {
|
||||
$chunks = preg_split("/\s+/", $child, 9);
|
||||
|
||||
if (isset($chunks[8]) && ($chunks[8] == '.' or $chunks[8] == '..')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($chunks) === 1) {
|
||||
$len = strlen($chunks[0]);
|
||||
|
||||
if ($len && $chunks[0][$len-1] == ':') {
|
||||
$path = substr($chunks[0], 0, -1);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Prepare for filename that has space
|
||||
$nameSlices = array_slice($chunks, 8, true);
|
||||
|
||||
$item = [
|
||||
'permissions' => $chunks[0],
|
||||
'number' => $chunks[1],
|
||||
'owner' => $chunks[2],
|
||||
'group' => $chunks[3],
|
||||
'size' => $chunks[4],
|
||||
'month' => $chunks[5],
|
||||
'day' => $chunks[6],
|
||||
'time' => $chunks[7],
|
||||
'name' => implode(' ', $nameSlices),
|
||||
'type' => $this->rawToType($chunks[0]),
|
||||
];
|
||||
|
||||
if ($item['type'] == 'link' && isset($chunks[10])) {
|
||||
$item['target'] = $chunks[10]; // 9 is "->"
|
||||
}
|
||||
|
||||
// if the key is not the path, behavior of ftp_rawlist() PHP function
|
||||
if (is_int($key) || false === strpos($key, $item['name'])) {
|
||||
array_splice($chunks, 0, 8);
|
||||
|
||||
$key = $item['type'].'#'
|
||||
.($path ? $path.'/' : '')
|
||||
.implode(' ', $chunks);
|
||||
|
||||
if ($item['type'] == 'link') {
|
||||
// get the first part of 'link#the-link.ext -> /path/of/the/source.ext'
|
||||
$exp = explode(' ->', $key);
|
||||
$key = rtrim($exp[0]);
|
||||
}
|
||||
|
||||
$items[$key] = $item;
|
||||
} else {
|
||||
// the key is the path, behavior of FtpClient::rawlist() method()
|
||||
$items[$key] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert raw info (drwx---r-x ...) to type (file, directory, link, unknown).
|
||||
* Only the first char is used for resolving.
|
||||
*
|
||||
* @param string $permission Example : drwx---r-x
|
||||
*
|
||||
* @return string The file type (file, directory, link, unknown)
|
||||
* @throws FtpException
|
||||
*/
|
||||
public function rawToType($permission)
|
||||
{
|
||||
if (!is_string($permission)) {
|
||||
throw new FtpException('The "$permission" argument must be a string, "'
|
||||
.gettype($permission).'" given.');
|
||||
}
|
||||
|
||||
if (empty($permission[0])) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
switch ($permission[0]) {
|
||||
case '-':
|
||||
return 'file';
|
||||
|
||||
case 'd':
|
||||
return 'directory';
|
||||
|
||||
case 'l':
|
||||
return 'link';
|
||||
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the wrapper which forward the PHP FTP functions to use in FtpClient instance.
|
||||
*
|
||||
* @param FtpWrapper $wrapper
|
||||
* @return FtpClient
|
||||
*/
|
||||
protected function setWrapper(FtpWrapper $wrapper)
|
||||
{
|
||||
$this->ftp = $wrapper;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of the `nicolab/php-ftp-client` package.
|
||||
*
|
||||
* (c) Nicolas Tallefourtane <dev@nicolab.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @copyright Nicolas Tallefourtane http://nicolab.net
|
||||
*/
|
||||
namespace FtpClient;
|
||||
|
||||
/**
|
||||
* The FtpException class.
|
||||
* Exception thrown if an error on runtime of the FTP client occurs.
|
||||
* @inheritDoc
|
||||
* @author Nicolas Tallefourtane <dev@nicolab.net>
|
||||
*/
|
||||
class FtpException extends \Exception {}
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of the `nicolab/php-ftp-client` package.
|
||||
*
|
||||
* (c) Nicolas Tallefourtane <dev@nicolab.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @copyright Nicolas Tallefourtane http://nicolab.net
|
||||
*/
|
||||
namespace FtpClient;
|
||||
|
||||
/**
|
||||
* Wrap the PHP FTP functions
|
||||
*
|
||||
* @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded
|
||||
* @method bool cdup() cdup() Changes to the parent directory
|
||||
* @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server
|
||||
* @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP
|
||||
* @method bool close() close() Closes an FTP connection
|
||||
* @method bool delete() delete(string $path) Deletes a file on the FTP server
|
||||
* @method bool exec() exec(string $command) Requests execution of a command on the FTP server
|
||||
* @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file
|
||||
* @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server
|
||||
* @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream
|
||||
* @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server
|
||||
* @method bool login() login(string $username, string $password) Logs in to an FTP connection
|
||||
* @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file
|
||||
* @method string mkdir() mkdir(string $directory) Creates a directory
|
||||
* @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking)
|
||||
* @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking)
|
||||
* @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking)
|
||||
* @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking)
|
||||
* @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking)
|
||||
* @method array nlist() nlist(string $directory) Returns a list of files in the given directory
|
||||
* @method bool pasv() pasv(bool $pasv) Turns passive mode on or off
|
||||
* @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server
|
||||
* @method string pwd() pwd() Returns the current directory name
|
||||
* @method bool quit() quit() Closes an FTP connection
|
||||
* @method array raw() raw(string $command) Sends an arbitrary command to an FTP server
|
||||
* @method array rawlist() rawlist(string $directory, bool $recursive = false) Returns a detailed list of files in the given directory
|
||||
* @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server
|
||||
* @method bool rmdir() rmdir(string $directory) Removes a directory
|
||||
* @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options
|
||||
* @method bool site() site(string $command) Sends a SITE command to the server
|
||||
* @method int size() size(string $remote_file) Returns the size of the given file
|
||||
* @method string systype() systype() Returns the system type identifier of the remote FTP server
|
||||
*
|
||||
* @author Nicolas Tallefourtane <dev@nicolab.net>
|
||||
*/
|
||||
class FtpWrapper
|
||||
{
|
||||
/**
|
||||
* The connection with the server
|
||||
*
|
||||
* @var resource
|
||||
*/
|
||||
protected $conn;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param resource &$connection The FTP (or SSL-FTP) connection (takes by reference).
|
||||
*/
|
||||
public function __construct(&$connection)
|
||||
{
|
||||
$this->conn = &$connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forward the method call to FTP functions
|
||||
*
|
||||
* @param string $function
|
||||
* @param array $arguments
|
||||
* @return mixed
|
||||
* @throws FtpException When the function is not valid
|
||||
*/
|
||||
public function __call($function, array $arguments)
|
||||
{
|
||||
$function = 'ftp_' . $function;
|
||||
|
||||
if (function_exists($function)) {
|
||||
array_unshift($arguments, $this->conn);
|
||||
return @call_user_func_array($function, $arguments);
|
||||
}
|
||||
|
||||
throw new FtpException("{$function} is not a valid FTP function");
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a FTP connection
|
||||
*
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @param int $timeout
|
||||
* @return resource
|
||||
*/
|
||||
public function connect($host, $port = 21, $timeout = 90)
|
||||
{
|
||||
return @ftp_connect($host, $port, $timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a Secure SSL-FTP connection
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @param int $timeout
|
||||
* @return resource
|
||||
*/
|
||||
public function ssl_connect($host, $port = 21, $timeout = 90)
|
||||
{
|
||||
return @ftp_ssl_connect($host, $port, $timeout);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
use \mageekguy\atoum;
|
||||
|
||||
$report = $script->addDefaultReport();
|
||||
|
||||
// This will add a green or red logo after each run depending on its status.
|
||||
$report->addField(new atoum\report\fields\runner\result\logo());
|
||||
|
||||
$script->bootstrapFile(__DIR__. '/bootstrap.php');
|
||||
$runner->addTestsFromDirectory(__DIR__. '/units');
|
||||
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
if (!defined('FTP_CLIENT_TEST_VENDOR')) {
|
||||
define('FTP_CLIENT_TEST_VENDOR', __DIR__ . '/../../../');
|
||||
}
|
||||
|
||||
$loader = require_once FTP_CLIENT_TEST_VENDOR . '/autoload.php';
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/*
|
||||
* This file is part of the `nicolab/php-ftp-client` package.
|
||||
*
|
||||
* (c) Nicolas Tallefourtane <dev@nicolab.net>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*
|
||||
* @copyright Nicolas Tallefourtane http://nicolab.net
|
||||
*/
|
||||
namespace tests\units\FtpClient;
|
||||
|
||||
use
|
||||
mageekguy\atoum,
|
||||
FtpClient\FtpClient as TestedClass
|
||||
;
|
||||
|
||||
/**
|
||||
* Tests the FtpClient\FtpClient class.
|
||||
* @author Nicolas Tallefourtane <dev@nicolab.net>
|
||||
*/
|
||||
class FtpClient extends atoum\test
|
||||
{
|
||||
|
||||
public function test__construct()
|
||||
{
|
||||
$this
|
||||
->given($ftp = new TestedClass())
|
||||
->object($ftp)
|
||||
->isInstanceOf('\FtpClient\FtpClient')
|
||||
;
|
||||
}
|
||||
}
|
||||