flysystem

This commit is contained in:
taoser 2023-02-12 18:22:24 +08:00
parent a056a37573
commit a420104b03
70 changed files with 3740 additions and 1354 deletions

View File

@ -243,8 +243,8 @@ abstract class BaseController
/**
* 获取文章链接地址
*
* @param integer $aid
* @param int $aid
* @param string $ename
* @return string
*/
protected function getRouteUrl(int $aid,string $ename = '') : string

View File

@ -77,15 +77,13 @@
{/block}
{block name="js"}
{:hook('bacimghook')}
<script>
layui.config({
base: '/static/admin/' //静态资源所在路径
}).extend({
index: 'lib/index' //主入口模块
}).use(['index', 'user'], function(){
var $ = layui.$
,setter = layui.setter
setter = layui.setter
,admin = layui.admin
,form = layui.form
,router = layui.router()
@ -136,6 +134,6 @@
});
});
</script>
{:hook('bacimghook')}
{/block}

View File

@ -28,6 +28,7 @@ var AdminLogin = "{:url('Login/index')}",
AdminLogout = "{:url('Admin/logout')}",
adminClearCache = "{:url('Admin/clearCache')}", sysCy = "{$clevel}";
var menuNav = "{:url('menu/getMenuNavbar')}";
var $ = layui.jquery;
</script>
{block name="js"}js文件{/block}
</body>

View File

@ -330,112 +330,4 @@ if (!function_exists('__')) {
}
}
if (!function_exists('setKeywords')) {
/**
* 关键词
* 通过百度分词接口获取关键词或者标签
* flag 1.为word时获取分词2.为tag时获取标签
*
* @return string
*/
function setKeywords($flag, $title, $content)
{
$keywords = [];
// 百度分词自动生成关键词
if(!empty(config('taoler.baidu.client_id')) == true) {
//headers数组内的格式
$headers = array();
$headers[] = "Content-Type:application/json";
switch($flag) {
//分词
case 'word':
$url = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/lexer?charset=UTF-8&access_token='.config('taoler.baidu.access_token');
$body = ["text" => $title];
break;
//标签
case 'tag':
$url = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/keyword?charset=UTF-8&access_token='.config('taoler.baidu.access_token');
$body = ['title' => $title, 'content'=> $content];
break;
default:
$url = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/lexer?charset=UTF-8&access_token='.config('taoler.baidu.access_token');
$body = ["text" => $title];
}
$postBody = json_encode($body);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);//设置请求头
curl_setopt($curl, CURLOPT_POSTFIELDS, $postBody);//设置请求体
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');//使用一个自定义的请求信息来代替"GET"或"HEAD"作为HTTP请求。(这个加不加没啥影响)
$datas = curl_exec($curl);
if($datas == false) {
echo '接口无法链接';
} else {
$res = stripos($datas,'error_code');
// 接收返回的数据
$dataItem = json_decode($datas);
if($res == false) {
// 数据正常
$items = $dataItem->items;
foreach($items as $item) {
switch($flag) {
case 'word':
if($item->pos == 'n' && !in_array($item->item,$keywords)){
$keywords[] = $item->item;
}
break;
case 'tag':
if(!in_array($item->tag,$keywords)){
$keywords[] = $item->tag;
}
break;
default:
if($item->pos == 'n' && !in_array($item->item,$keywords)){
$keywords[] = $item->item;
}
}
}
} else {
// 接口正常但获取数据失败可能参数错误重新获取token
$url = 'https://aip.baidubce.com/oauth/2.0/token';
$post_data['grant_type'] = config('taoler.baidu.grant_type');;
$post_data['client_id'] = config('taoler.baidu.client_id');
$post_data['client_secret'] = config('taoler.baidu.client_secret');
$o = "";
foreach ( $post_data as $k => $v )
{
$o.= "$k=" . urlencode( $v ). "&" ;
}
$postData = substr($o,0,-1);
$curl = curl_init();//初始化curl
curl_setopt($curl, CURLOPT_URL,$url);//抓取指定网页
curl_setopt($curl, CURLOPT_HEADER, 0);//设置header
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
curl_setopt($curl, CURLOPT_POST, 1);//post提交方式
curl_setopt($curl, CURLOPT_POSTFIELDS, $postData);
$data = curl_exec($curl);//运行curl
curl_close($curl);
// 写入token
SetArr::name('taoler')->edit([
'baidu'=> [
'access_token' => json_decode($data)->access_token,
]
]);
//echo 'api接口数据错误 - ';
//echo $dataItem->error_msg;
}
}
}
return implode(",", $keywords);
}
}

373
app/common/lib/Arts.php Normal file
View File

@ -0,0 +1,373 @@
<?php
namespace app\common\lib;
use taoser\SetArr;
use think\facade\Db;
use think\facade\Lang;
use think\facade\Request;
use think\response\Json;
class Arts
{
//显示网站设置
protected function getSystem()
{
//1.系统配置信息
return Db::name('system')->cache('system',3600)->find(1);
}
//域名协议转换 把数据库中的带HTTP或不带协议的域名转换为当前协议的域名前缀
protected function getHttpUrl($url)
{
//域名转换为无http协议
$www = stripos($url,'://') ? substr(stristr($url,'://'),3) : $url;
return Request::scheme().'://'. $www;
}
//得到当前系统安装前台域名
protected function getIndexUrl()
{
$sys = $this->getSystem();
return $this->getHttpUrl($sys['domain']);
}
/**
* 获取文章链接地址
* @param int $aid
* @param string $ename
* @return string
*/
public function getRouteUrl(int $aid,string $ename = '') : string
{
$indexUrl = $this->getIndexUrl();
if(config('taoler.url_rewrite.article_as') == '<ename>/'){
// 分类可变路由
$artUrl = (string) url('article_detail', ['id' => (int) $aid, 'ename'=> $ename]);
//$artUrl = (string) Route::buildUrl('article_detail', ['id' => $aid, 'ename'=> $ename]);
} else {
$artUrl = (string) url('article_detail', ['id' => $aid]);
}
//dump($artUrl);
// 判断是否开启绑定
//$domain_bind = array_key_exists('domain_bind',config('app'));
// 判断index应用是否绑定域名
$bind_index = array_search('index',config('app.domain_bind'));
// 判断admin应用是否绑定域名
$bind_admin = array_search('admin',config('app.domain_bind'));
// 判断index应用是否域名映射
$map_index = array_search('index',config('app.app_map'));
// 判断admin应用是否域名映射
$map_admin = array_search('admin',config('app.app_map'));
$index = $map_index ? $map_index : 'index'; // index应用名
$admin = $map_admin ? $map_admin : 'admin'; // admin应用名
if($bind_index) {
// index绑定域名
$url = $indexUrl . str_replace($admin.'/','',$artUrl);
} else { // index未绑定域名
// admin绑定域名
if($bind_admin) {
$url = $indexUrl .'/' . $index . $artUrl;
} else {
$url = $indexUrl . str_replace($admin,$index,$artUrl);
}
}
return $url;
}
/**
* 关键词
* 通过百度分词接口获取关键词或者标签
* @param $flag string 为word时获取分词2.为tag时获取标签
* @param $title string
* @param $content string
* @return array
*/
public function setKeywords(string $flag,string $title,string $content) :array
{
$keywords = [];
// 百度分词自动生成关键词
if(!empty(config('taoler.baidu.client_id'))) {
//headers数组内的格式
$headers = [];
$headers[] = "Content-Type:application/json";
switch($flag) {
//分词
case 'word':
$url = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/lexer?charset=UTF-8&access_token='.config('taoler.baidu.access_token');
$body = ["text" => $title];
break;
//标签
case 'tag':
$url = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/keyword?charset=UTF-8&access_token='.config('taoler.baidu.access_token');
$body = ['title' => $title, 'content' => $content];
break;
default:
$url = 'https://aip.baidubce.com/rpc/2.0/nlp/v1/lexer?charset=UTF-8&access_token='.config('taoler.baidu.access_token');
$body = ["text" => $title];
}
$postBody = json_encode($body);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);//设置请求头
curl_setopt($curl, CURLOPT_POSTFIELDS, $postBody);//设置请求体
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST');//使用一个自定义的请求信息来代替"GET"或"HEAD"作为HTTP请求。(这个加不加没啥影响)
$datas = curl_exec($curl);
if($datas == false) {
echo '接口无法链接';
} else {
$res = stripos($datas,'error_code');
// 接收返回的数据
$dataItem = json_decode($datas);
if($res == false) {
// 数据正常
$items = $dataItem->items;
foreach($items as $item) {
switch($flag) {
case 'word':
if($item->pos == 'n' && !in_array($item->item,$keywords)){
$keywords[] = $item->item;
}
break;
case 'tag':
if(!in_array($item->tag,$keywords)){
$keywords[] = $item->tag;
}
break;
default:
if($item->pos == 'n' && !in_array($item->item,$keywords)){
$keywords[] = $item->item;
}
}
}
} else {
// 接口正常但获取数据失败可能参数错误重新获取token
$url = 'https://aip.baidubce.com/oauth/2.0/token';
$post_data['grant_type'] = config('taoler.baidu.grant_type');;
$post_data['client_id'] = config('taoler.baidu.client_id');
$post_data['client_secret'] = config('taoler.baidu.client_secret');
$o = "";
foreach ( $post_data as $k => $v )
{
$o.= "$k=" . urlencode( $v ). "&" ;
}
$post_data = substr($o,0,-1);
$res = $this->request_post($url, $post_data);
// 写入token
SetArr::name('taoler')->edit([
'baidu'=> [
'access_token' => json_decode($res)->access_token,
]
]);
echo 'api接口数据错误 - ';
echo $dataItem->error_msg;
}
}
}
return $keywords;
}
// api_post接口
function request_post($url = '', $param = '')
{
if (empty($url) || empty($param)) {
return false;
}
$postUrl = $url;
$curlPost = $param;
$curl = curl_init();//初始化curl
curl_setopt($curl, CURLOPT_URL,$postUrl);//抓取指定网页
curl_setopt($curl, CURLOPT_HEADER, 0);//设置header
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);//要求结果为字符串且输出到屏幕上
curl_setopt($curl, CURLOPT_POST, 1);//post提交方式
curl_setopt($curl, CURLOPT_POSTFIELDS, $curlPost);
$data = curl_exec($curl);//运行curl
curl_close($curl);
return $data;
}
/**
* 标题调用百度关键词词条
*
* @return Json
*/
public function getBdiduSearchWordList($words)
{
if(empty($words)) return json(['code'=>-1,'msg'=>'null']);
$url = 'https://www.baidu.com/sugrec?prod=pc&from=pc_web&wd='.$words;
//$result = Api::urlGet($url);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$datas = curl_exec($curl);
curl_close($curl);
$data = json_decode($datas,true);
if(isset($data['g'])) {
return json(['code'=>0,'msg'=>'success','data'=>$data['g']]);
} else {
return json(['code'=>-1,'msg'=>'null']);
}
}
/**
* baidu push api
*
* @param string $link
* @return void
*/
public function baiduPushUrl(string $link)
{
// baidu 接口
$api = config('taoler.baidu.push_api');
if(!empty($api)) {
$url[] = $link;
$ch = curl_init();
$options = array(
CURLOPT_URL => $api,
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => implode("\n", $url),
CURLOPT_HTTPHEADER => array('Content-Type: text/plain'),
);
curl_setopt_array($ch, $options);
curl_exec($ch);
curl_close($ch);
}
}
/**
* 上传接口
*
* @return void
*/
public function uploadFiles($type)
{
$uploads = new Uploads();
switch ($type){
case 'image':
$upRes = $uploads->put('file','article_pic',2048,'image');
break;
case 'zip':
$upRes = $uploads->put('file','article_zip',1024,'application|image');
break;
case 'video':
$upRes = $uploads->put('file','article_video',102400,'video|audio');
break;
case 'audio':
$upRes = $uploads->put('file','article_audio',102400,'audio');
break;
default:
$upRes = $uploads->put('file','article_file',2048,'image');
break;
}
return $upRes;
}
//获取artcile内容所有图片返回数组
protected function getArticleAllpic($str)
{
//正则匹配<img src="http://img.com" />
$pattern = "/<[img|IMG].*?src=[\'|\"](.*?(?:[\.gif|\.jpg|\.png]))[\'|\"].*?[\/]?>/";
preg_match_all($pattern,$str,$matchContent);
if(isset($matchContent[1])){
$img = $matchContent[1];
}else{
$temp = "./images/no-image.jpg";//在相应位置放置一张命名为no-image的jpg图片
}
return $img;
}
//下载远程图片
private function downloadImage($url)
{
$ch = curl_init();
curl_setopt ($ch, CURLOPT_CUSTOMREQUEST, 'GET' );
curl_setopt ($ch, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
$file = curl_exec($ch);
curl_close($ch);
return $this->saveAsImage($url, $file);
}
//把图片保存到本地
private function saveAsImage($url, $file)
{
$filename = pathinfo($url, PATHINFO_BASENAME);
//$dirname = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_DIRNAME);
$dirname = date('Ymd',time());
//路径
$path = 'storage/' . $this->uid . '/article_pic/' . $dirname . '/';
//绝对文件夹
$fileDir = public_path() . $path;
//文件绝对路径
$filePath = $fileDir . $filename;
//相对路径文件名
$realFile = '/' . $path . $filename;
// 如果目录不存在,则创建
if(!is_dir($fileDir)) {
mkdir($fileDir, 0777, true);
}
if(file_exists($filePath)) {
//$this->output->writeln("【已存在】输出路径" . $fullpath);
return $realFile;
} else {
$resource = fopen($filePath, 'a');
$res = fwrite($resource, $file);
fclose($resource);
if($res !== false) {
return $realFile;
}
}
}
//下载网络图片到本地并替换
public function downUrlPicsReaplace($content)
{
// 批量下载网络图片并替换
$images = $this->getArticleAllpic($content);
if(count($images)) {
foreach($images as $image){
//1.带http地址的图片2.非本站的网络图片 3.非带有?号等参数的图片
if((stripos($image,'http') !== false) && (stripos($image, Request::domain()) === false) && (stripos($image, '?') === false)) {
// 如果图片中没有带参数或者加密可下载
//下载远程图片(可下载)
$newImageUrl = $this->downloadImage($image);
//替换图片链接
$content = str_replace($image,Request::domain().$newImageUrl,$content);
}
}
//不可下载的图片如加密或者带有参数的图片如type=jpeg,直接返回content
}
return $content;
}
}

View File

@ -39,7 +39,8 @@
"workerman/phpsocket.io": "^1.1",
"jaeger/querylist": "^4.2",
"symfony/var-exporter": "^5.4",
"yzh52521/easyhttp": "^1.0"
"yzh52521/easyhttp": "^1.0",
"topthink/think-filesystem": "^2.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",

130
composer.lock generated
View File

@ -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": "20e9c0c21d94d1cbf1c29c69868ebe38",
"content-hash": "76842e3f55a3ecc7f87e51236f14622d",
"packages": [
{
"name": "bacon/bacon-qr-code",
@ -140,7 +140,7 @@
},
"require": {
"cache/adapter-common": "^1.0",
"league/flysystem": "^1.0",
"league/flysystem": "^1.0 || ^2.0",
"php": ">=7.4",
"psr/cache": "^1.0 || ^2.0",
"psr/simple-cache": "^1.0"
@ -920,54 +920,44 @@
},
{
"name": "league/flysystem",
"version": "1.1.10",
"version": "2.5.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1"
"reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1",
"reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8aaffb653c5777781b0f7f69a5d937baf7ab6cdb",
"reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"league/mime-type-detection": "^1.3",
"php": "^7.2.5 || ^8.0"
"ext-json": "*",
"league/mime-type-detection": "^1.0.0",
"php": "^7.2 || ^8.0"
},
"conflict": {
"league/flysystem-sftp": "<1.0.6"
"guzzlehttp/ringphp": "<1.1.1"
},
"require-dev": {
"phpspec/prophecy": "^1.11.1",
"phpunit/phpunit": "^8.5.8"
},
"suggest": {
"ext-ftp": "Allows you to use FTP server storage",
"ext-openssl": "Allows you to use FTPS server storage",
"league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
"league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
"league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
"league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
"league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
"league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
"league/flysystem-webdav": "Allows you to use WebDAV storage",
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
"srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
"async-aws/s3": "^1.5",
"async-aws/simple-s3": "^1.0",
"aws/aws-sdk-php": "^3.132.4",
"composer/semver": "^3.0",
"ext-fileinfo": "*",
"ext-ftp": "*",
"friendsofphp/php-cs-fixer": "^3.2",
"google/cloud-storage": "^1.23",
"phpseclib/phpseclib": "^2.0",
"phpstan/phpstan": "^0.12.26",
"phpunit/phpunit": "^8.5 || ^9.4",
"sabre/dav": "^4.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src/"
"League\\Flysystem\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -977,40 +967,42 @@
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frenky.net"
"email": "info@frankdejonge.nl"
}
],
"description": "Filesystem abstraction: Many filesystems, one API.",
"description": "File storage abstraction for PHP",
"keywords": [
"Cloud Files",
"WebDAV",
"abstraction",
"aws",
"cloud",
"copy.com",
"dropbox",
"file systems",
"file",
"files",
"filesystem",
"filesystems",
"ftp",
"rackspace",
"remote",
"s3",
"sftp",
"storage"
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/1.1.10"
"source": "https://github.com/thephpleague/flysystem/tree/2.5.0"
},
"funding": [
{
"url": "https://offset.earth/frankdejonge",
"type": "other"
"url": "https://ecologi.com/frankdejonge",
"type": "custom"
},
{
"url": "https://github.com/frankdejonge",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/league/flysystem",
"type": "tidelift"
}
],
"time": "2022-10-04T09:16:37+00:00"
"time": "2022-09-17T21:02:32+00:00"
},
{
"name": "league/mime-type-detection",
@ -2561,6 +2553,52 @@
},
"time": "2022-10-26T07:59:42+00:00"
},
{
"name": "topthink/think-filesystem",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/top-think/think-filesystem.git",
"reference": "63e525fd74f451b2df1df060c3194e9b6e724730"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/top-think/think-filesystem/zipball/63e525fd74f451b2df1df060c3194e9b6e724730",
"reference": "63e525fd74f451b2df1df060c3194e9b6e724730",
"shasum": ""
},
"require": {
"league/flysystem": "^2.0",
"topthink/framework": "^6.1"
},
"require-dev": {
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"think\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "yunwuxin",
"email": "448901948@qq.com"
}
],
"description": "The ThinkPHP6.1 Filesystem Package",
"support": {
"issues": "https://github.com/top-think/think-filesystem/issues",
"source": "https://github.com/top-think/think-filesystem/tree/v2.0.0"
},
"time": "2022-10-26T04:51:41+00:00"
},
{
"name": "topthink/think-helper",
"version": "v3.1.6",

View File

@ -16,7 +16,7 @@ return [
// 应用名,此项不可更改
'appname' => 'TaoLer',
// 版本配置
'version' => '2.0.8',
'version' => '2.0.9',
// 加盐
'salt' => 'taoler',
// 数据库备份目录

17
vendor/autoload.php vendored
View File

@ -3,8 +3,21 @@
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
exit(1);
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';

View File

@ -14,7 +14,7 @@ return array(
'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'),
'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'),
'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'),
'think\\' => array($vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-template/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/framework/src/think'),
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src', $vendorDir . '/topthink/think-filesystem/src'),
'taoser\\think\\' => array($vendorDir . '/taoser/think-auth/src'),
'taoser\\' => array($vendorDir . '/taoser/think-addons/src', $vendorDir . '/taoser/think-setarr/src'),
'phpspirit\\databackup\\' => array($vendorDir . '/lotofbadcode/phpspirit_databackup/src'),

View File

@ -180,10 +180,11 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
),
'think\\' =>
array (
0 => __DIR__ . '/..' . '/topthink/think-helper/src',
1 => __DIR__ . '/..' . '/topthink/think-template/src',
0 => __DIR__ . '/..' . '/topthink/framework/src/think',
1 => __DIR__ . '/..' . '/topthink/think-helper/src',
2 => __DIR__ . '/..' . '/topthink/think-orm/src',
3 => __DIR__ . '/..' . '/topthink/framework/src/think',
3 => __DIR__ . '/..' . '/topthink/think-template/src',
4 => __DIR__ . '/..' . '/topthink/think-filesystem/src',
),
'taoser\\think\\' =>
array (

View File

@ -962,57 +962,47 @@
},
{
"name": "league/flysystem",
"version": "1.1.10",
"version_normalized": "1.1.10.0",
"version": "2.5.0",
"version_normalized": "2.5.0.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem.git",
"reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1"
"reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/3239285c825c152bcc315fe0e87d6b55f5972ed1",
"reference": "3239285c825c152bcc315fe0e87d6b55f5972ed1",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/8aaffb653c5777781b0f7f69a5d937baf7ab6cdb",
"reference": "8aaffb653c5777781b0f7f69a5d937baf7ab6cdb",
"shasum": ""
},
"require": {
"ext-fileinfo": "*",
"league/mime-type-detection": "^1.3",
"php": "^7.2.5 || ^8.0"
"ext-json": "*",
"league/mime-type-detection": "^1.0.0",
"php": "^7.2 || ^8.0"
},
"conflict": {
"league/flysystem-sftp": "<1.0.6"
"guzzlehttp/ringphp": "<1.1.1"
},
"require-dev": {
"phpspec/prophecy": "^1.11.1",
"phpunit/phpunit": "^8.5.8"
"async-aws/s3": "^1.5",
"async-aws/simple-s3": "^1.0",
"aws/aws-sdk-php": "^3.132.4",
"composer/semver": "^3.0",
"ext-fileinfo": "*",
"ext-ftp": "*",
"friendsofphp/php-cs-fixer": "^3.2",
"google/cloud-storage": "^1.23",
"phpseclib/phpseclib": "^2.0",
"phpstan/phpstan": "^0.12.26",
"phpunit/phpunit": "^8.5 || ^9.4",
"sabre/dav": "^4.1"
},
"suggest": {
"ext-ftp": "Allows you to use FTP server storage",
"ext-openssl": "Allows you to use FTPS server storage",
"league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
"league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
"league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
"league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
"league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
"league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
"league/flysystem-webdav": "Allows you to use WebDAV storage",
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
"srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
},
"time": "2022-10-04T09:16:37+00:00",
"time": "2022-09-17T21:02:32+00:00",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src/"
"League\\Flysystem\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -1022,37 +1012,39 @@
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frenky.net"
"email": "info@frankdejonge.nl"
}
],
"description": "Filesystem abstraction: Many filesystems, one API.",
"description": "File storage abstraction for PHP",
"keywords": [
"Cloud Files",
"WebDAV",
"abstraction",
"aws",
"cloud",
"copy.com",
"dropbox",
"file systems",
"file",
"files",
"filesystem",
"filesystems",
"ftp",
"rackspace",
"remote",
"s3",
"sftp",
"storage"
],
"support": {
"issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/1.1.10"
"source": "https://github.com/thephpleague/flysystem/tree/2.5.0"
},
"funding": [
{
"url": "https://offset.earth/frankdejonge",
"type": "other"
"url": "https://ecologi.com/frankdejonge",
"type": "custom"
},
{
"url": "https://github.com/frankdejonge",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/league/flysystem",
"type": "tidelift"
}
],
"install-path": "../league/flysystem"
@ -2716,6 +2708,55 @@
},
"install-path": "../topthink/think-captcha"
},
{
"name": "topthink/think-filesystem",
"version": "v2.0.0",
"version_normalized": "2.0.0.0",
"source": {
"type": "git",
"url": "https://github.com/top-think/think-filesystem.git",
"reference": "63e525fd74f451b2df1df060c3194e9b6e724730"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/top-think/think-filesystem/zipball/63e525fd74f451b2df1df060c3194e9b6e724730",
"reference": "63e525fd74f451b2df1df060c3194e9b6e724730",
"shasum": ""
},
"require": {
"league/flysystem": "^2.0",
"topthink/framework": "^6.1"
},
"require-dev": {
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^8.0"
},
"time": "2022-10-26T04:51:41+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"think\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "yunwuxin",
"email": "448901948@qq.com"
}
],
"description": "The ThinkPHP6.1 Filesystem Package",
"support": {
"issues": "https://github.com/top-think/think-filesystem/issues",
"source": "https://github.com/top-think/think-filesystem/tree/v2.0.0"
},
"install-path": "../topthink/think-filesystem"
},
{
"name": "topthink/think-helper",
"version": "v3.1.6",

View File

@ -3,7 +3,7 @@
'name' => 'taoser/taoler',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'd0f1021f8dc728187d75d1722deac92d73a25d82',
'reference' => '6bb4a77619f0244cc914b917f5a95ade44c7500f',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -137,9 +137,9 @@
'dev_requirement' => false,
),
'league/flysystem' => array(
'pretty_version' => '1.1.10',
'version' => '1.1.10.0',
'reference' => '3239285c825c152bcc315fe0e87d6b55f5972ed1',
'pretty_version' => '2.5.0',
'version' => '2.5.0.0',
'reference' => '8aaffb653c5777781b0f7f69a5d937baf7ab6cdb',
'type' => 'library',
'install_path' => __DIR__ . '/../league/flysystem',
'aliases' => array(),
@ -358,7 +358,7 @@
'taoser/taoler' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'd0f1021f8dc728187d75d1722deac92d73a25d82',
'reference' => '6bb4a77619f0244cc914b917f5a95ade44c7500f',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -418,6 +418,15 @@
'aliases' => array(),
'dev_requirement' => false,
),
'topthink/think-filesystem' => array(
'pretty_version' => 'v2.0.0',
'version' => '2.0.0.0',
'reference' => '63e525fd74f451b2df1df060c3194e9b6e724730',
'type' => 'library',
'install_path' => __DIR__ . '/../topthink/think-filesystem',
'aliases' => array(),
'dev_requirement' => false,
),
'topthink/think-helper' => array(
'pretty_version' => 'v3.1.6',
'version' => '3.1.6.0',

2
vendor/league/flysystem/INFO.md vendored Normal file
View File

@ -0,0 +1,2 @@
View the docs at: https://flysystem.thephpleague.com/v2/
Changelog at: https://github.com/thephpleague/flysystem/blob/2.x/CHANGELOG.md

View File

@ -1,4 +1,4 @@
Copyright (c) 2013-2019 Frank de Jonge
Copyright (c) 2013-2022 Frank de Jonge
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -1,68 +1,48 @@
{
"name": "league/flysystem",
"type": "library",
"description": "Filesystem abstraction: Many filesystems, one API.",
"description": "File storage abstraction for PHP",
"keywords": [
"filesystem", "filesystems", "files", "storage", "dropbox", "aws",
"abstraction", "s3", "ftp", "sftp", "remote", "webdav",
"file systems", "cloud", "cloud files", "rackspace", "copy.com"
"filesystem", "filesystems", "files", "storage", "aws",
"s3", "ftp", "sftp", "webdav", "file", "cloud"
],
"funding": [
{
"type": "other",
"url": "https://offset.earth/frankdejonge"
"scripts": {
"phpstan": "vendor/bin/phpstan analyse -l 6 src"
},
"type": "library",
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src"
}
],
},
"require": {
"php": "^7.2 || ^8.0",
"ext-json": "*",
"league/mime-type-detection": "^1.0.0"
},
"require-dev": {
"ext-fileinfo": "*",
"ext-ftp": "*",
"phpunit/phpunit": "^8.5 || ^9.4",
"phpstan/phpstan": "^0.12.26",
"phpseclib/phpseclib": "^2.0",
"aws/aws-sdk-php": "^3.132.4",
"composer/semver": "^3.0",
"friendsofphp/php-cs-fixer": "^3.2",
"google/cloud-storage": "^1.23",
"async-aws/s3": "^1.5",
"async-aws/simple-s3": "^1.0",
"sabre/dav": "^4.1"
},
"conflict": {
"guzzlehttp/ringphp": "<1.1.1"
},
"license": "MIT",
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frenky.net"
"email": "info@frankdejonge.nl"
}
],
"require": {
"php": "^7.2.5 || ^8.0",
"ext-fileinfo": "*",
"league/mime-type-detection": "^1.3"
},
"require-dev": {
"phpspec/prophecy": "^1.11.1",
"phpunit/phpunit": "^8.5.8"
},
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"League\\Flysystem\\Stub\\": "stub/"
}
},
"suggest": {
"league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
"league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
"league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
"league/flysystem-webdav": "Allows you to use WebDAV storage",
"league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
"league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
"spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
"srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications",
"league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
"ext-ftp": "Allows you to use FTP server storage",
"ext-openssl": "Allows you to use FTPS server storage",
"league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
"league/flysystem-ziparchive": "Allows you to use ZipArchive adapter"
},
"conflict": {
"league/flysystem-sftp": "<1.0.6"
},
"extra": {
"branch-alias": {
"dev-master": "1.1-dev"
}
},
"scripts": {
"phpstan": "php phpstan.php"
}
]
}

View File

@ -0,0 +1,49 @@
{
"sub-splits": [
{
"name": "ftp",
"directory": "src/Ftp",
"target": "git@github.com:thephpleague/flysystem-ftp.git"
},
{
"name": "sftp",
"directory": "src/PhpseclibV2",
"target": "git@github.com:thephpleague/flysystem-sftp.git"
},
{
"name": "sftp-v3",
"directory": "src/PhpseclibV3",
"target": "git@github.com:thephpleague/flysystem-sftp-v3.git"
},
{
"name": "memory",
"directory": "src/InMemory",
"target": "git@github.com:thephpleague/flysystem-memory.git"
},
{
"name": "ziparchive",
"directory": "src/ZipArchive",
"target": "git@github.com:thephpleague/flysystem-ziparchive.git"
},
{
"name": "aws-s3-v3",
"directory": "src/AwsS3V3",
"target": "git@github.com:thephpleague/flysystem-aws-s3-v3.git"
},
{
"name": "async-aws-s3",
"directory": "src/AsyncAwsS3",
"target": "git@github.com:thephpleague/flysystem-async-aws-s3.git"
},
{
"name": "google-cloud-storage",
"directory": "src/GoogleCloudStorage",
"target": "git@github.com:thephpleague/flysystem-google-cloud-storage.git"
},
{
"name": "adapter-test-utilities",
"directory": "src/AdapterTestUtilities",
"target": "git@github.com:thephpleague/flysystem-adapter-test-utilities.git"
}
]
}

View File

@ -0,0 +1,58 @@
---
version: "3"
services:
webdav:
image: bytemark/webdav
restart: always
ports:
- "80:80"
environment:
AUTH_TYPE: Digest
USERNAME: alice
PASSWORD: secret1234
sftp:
container_name: sftp
restart: always
image: atmoz/sftp
volumes:
- ./test_files/sftp/users.conf:/etc/sftp/users.conf
- ./test_files/sftp/ssh_host_ed25519_key:/etc/ssh/ssh_host_ed25519_key
- ./test_files/sftp/ssh_host_rsa_key:/etc/ssh/ssh_host_rsa_key
- ./test_files/sftp/id_rsa.pub:/home/bar/.ssh/keys/id_rsa.pub
ports:
- "2222:22"
ftp:
container_name: ftp
restart: always
image: delfer/alpine-ftp-server
environment:
USERS: 'foo|pass|/home/foo/upload'
ADDRESS: 'localhost'
ports:
- "2121:21"
- "21000-21010:21000-21010"
ftpd:
container_name: ftpd
restart: always
environment:
PUBLICHOST: localhost
FTP_USER_NAME: foo
FTP_USER_PASS: pass
FTP_USER_HOME: /home/foo
image: stilliard/pure-ftpd
ports:
- "2122:21"
- "30000-30009:30000-30009"
command: "/run.sh -l puredb:/etc/pure-ftpd/pureftpd.pdb -E -j -P localhost"
toxiproxy:
container_name: toxiproxy
restart: unless-stopped
image: ghcr.io/shopify/toxiproxy
command: "-host 0.0.0.0 -config /opt/toxiproxy/config.json"
volumes:
- ./test_files/toxiproxy/toxiproxy.json:/opt/toxiproxy/config.json:ro
ports:
- "8474:8474" # HTTP API
- "8222:8222" # SFTP
- "8121:8121" # FTP
- "8122:8122" # FTPD

45
vendor/league/flysystem/readme.md vendored Normal file
View File

@ -0,0 +1,45 @@
# League\Flysystem
[![Author](https://img.shields.io/badge/author-@frankdejonge-blue.svg)](https://twitter.com/frankdejonge)
[![Source Code](https://img.shields.io/badge/source-thephpleague/flysystem-blue.svg)](https://github.com/thephpleague/flysystem)
[![Latest Version](https://img.shields.io/github/tag/thephpleague/flysystem.svg)](https://github.com/thephpleague/flysystem/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/thephpleague/flysystem/blob/master/LICENSE)
[![Quality Assurance](https://github.com/thephpleague/flysystem/workflows/Quality%20Assurance/badge.svg?branch=2.x)](https://github.com/thephpleague/flysystem/actions?query=workflow%3A%22Quality+Assurance%22)
[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem.svg)](https://packagist.org/packages/league/flysystem)
![php 7.2+](https://img.shields.io/badge/php-min%207.2-red.svg)
## About Flysystem
Flysystem is a file storage library for PHP. It provides one interface to
interact with many types of filesystems. When you use Flysystem, you're
not only protected from vendor lock-in, you'll also have a consistent experience
for which ever storage is right for you.
## Getting Started
* **[New in V2](https://flysystem.thephpleague.com/v2/docs/what-is-new/)**: What it new in Flysystem V2?
* **[Architecture](https://flysystem.thephpleague.com/v2/docs/architecture/)**: Flysystem's internal architecture
* **[Flysystem API](https://flysystem.thephpleague.com/v2/docs/usage/filesystem-api/)**: How to interact with your Flysystem instance
* **[Upgrade to V2](https://flysystem.thephpleague.com/v2/docs/advanced/upgrade-to-2.0.0/)**: How to upgrade your Flysystem V1 instance to V2
### Commonly-Used Adapters
* **[AsyncAws S3](https://flysystem.thephpleague.com/v2/docs/adapter/async-aws-s3/)**
* **[AWS S3](https://flysystem.thephpleague.com/v2/docs/adapter/aws-s3-v3/)**
* **[Local](https://flysystem.thephpleague.com/v2/docs/adapter/local/)**
* **[Memory](https://flysystem.thephpleague.com/v2/docs/adapter/in-memory/)**
### Third party Adapters
* **[Gitlab](https://github.com/RoyVoetman/flysystem-gitlab-storage)**
* **[Google Drive (using regular paths)](https://github.com/masbug/flysystem-google-drive-ext)**
You can always [create an adapter](https://flysystem.thephpleague.com/v2/docs/advanced/creating-an-adapter/) yourself.
## Security
If you discover any security related issues, please email info@frankdejonge.nl instead of using the issue tracker.
## Enjoy
Oh, and if you've come down this far, you might as well follow me on [twitter](https://twitter.com/frankdejonge).

View File

@ -1,107 +1,43 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use function array_merge;
class Config
{
public const OPTION_VISIBILITY = 'visibility';
public const OPTION_DIRECTORY_VISIBILITY = 'directory_visibility';
/**
* @var array
*/
protected $settings = [];
private $options;
/**
* @var Config|null
*/
protected $fallback;
/**
* Constructor.
*
* @param array $settings
*/
public function __construct(array $settings = [])
public function __construct(array $options = [])
{
$this->settings = $settings;
$this->options = $options;
}
/**
* Get a setting.
* @param mixed $default
*
* @param string $key
* @param mixed $default
*
* @return mixed config setting or default when not found
* @return mixed
*/
public function get($key, $default = null)
public function get(string $property, $default = null)
{
if ( ! array_key_exists($key, $this->settings)) {
return $this->getDefault($key, $default);
}
return $this->settings[$key];
return $this->options[$property] ?? $default;
}
/**
* Check if an item exists by key.
*
* @param string $key
*
* @return bool
*/
public function has($key)
public function extend(array $options): Config
{
if (array_key_exists($key, $this->settings)) {
return true;
}
return $this->fallback instanceof Config
? $this->fallback->has($key)
: false;
return new Config(array_merge($this->options, $options));
}
/**
* Try to retrieve a default setting from a config fallback.
*
* @param string $key
* @param mixed $default
*
* @return mixed config setting or default when not found
*/
protected function getDefault($key, $default)
public function withDefaults(array $defaults): Config
{
if ( ! $this->fallback) {
return $default;
}
return $this->fallback->get($key, $default);
}
/**
* Set a setting.
*
* @param string $key
* @param mixed $value
*
* @return $this
*/
public function set($key, $value)
{
$this->settings[$key] = $value;
return $this;
}
/**
* Set the fallback.
*
* @param Config $fallback
*
* @return $this
*/
public function setFallback(Config $fallback)
{
$this->fallback = $fallback;
return $this;
return new Config($this->options + $defaults);
}
}

View File

@ -2,15 +2,11 @@
namespace League\Flysystem;
use LogicException;
use RuntimeException;
class CorruptedPathDetected extends LogicException implements FilesystemException
final class CorruptedPathDetected extends RuntimeException implements FilesystemException
{
/**
* @param string $path
* @return CorruptedPathDetected
*/
public static function forPath($path)
public static function forPath(string $path): CorruptedPathDetected
{
return new CorruptedPathDetected("Corrupted path detected: " . $path);
}

View File

@ -0,0 +1,110 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class DirectoryAttributes implements StorageAttributes
{
use ProxyArrayAccessToProperties;
/**
* @var string
*/
private $type = StorageAttributes::TYPE_DIRECTORY;
/**
* @var string
*/
private $path;
/**
* @var string|null
*/
private $visibility;
/**
* @var int|null
*/
private $lastModified;
/**
* @var array
*/
private $extraMetadata;
public function __construct(string $path, ?string $visibility = null, ?int $lastModified = null, array $extraMetadata = [])
{
$this->path = $path;
$this->visibility = $visibility;
$this->lastModified = $lastModified;
$this->extraMetadata = $extraMetadata;
}
public function path(): string
{
return $this->path;
}
public function type(): string
{
return StorageAttributes::TYPE_DIRECTORY;
}
public function visibility(): ?string
{
return $this->visibility;
}
public function lastModified(): ?int
{
return $this->lastModified;
}
public function extraMetadata(): array
{
return $this->extraMetadata;
}
public function isFile(): bool
{
return false;
}
public function isDir(): bool
{
return true;
}
public function withPath(string $path): StorageAttributes
{
$clone = clone $this;
$clone->path = $path;
return $clone;
}
public static function fromArray(array $attributes): StorageAttributes
{
return new DirectoryAttributes(
$attributes[StorageAttributes::ATTRIBUTE_PATH],
$attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
);
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
return [
StorageAttributes::ATTRIBUTE_TYPE => $this->type,
StorageAttributes::ATTRIBUTE_PATH => $this->path,
StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
];
}
}

View File

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use ArrayIterator;
use Generator;
use IteratorAggregate;
use Traversable;
/**
* @template T
*/
class DirectoryListing implements IteratorAggregate
{
/**
* @var iterable<T>
*/
private $listing;
/**
* @param iterable<T> $listing
*/
public function __construct(iterable $listing)
{
$this->listing = $listing;
}
public function filter(callable $filter): DirectoryListing
{
$generator = (static function (iterable $listing) use ($filter): Generator {
foreach ($listing as $item) {
if ($filter($item)) {
yield $item;
}
}
})($this->listing);
return new DirectoryListing($generator);
}
public function map(callable $mapper): DirectoryListing
{
$generator = (static function (iterable $listing) use ($mapper): Generator {
foreach ($listing as $item) {
yield $mapper($item);
}
})($this->listing);
return new DirectoryListing($generator);
}
public function sortByPath(): DirectoryListing
{
$listing = $this->toArray();
usort($listing, function (StorageAttributes $a, StorageAttributes $b) {
return $a->path() <=> $b->path();
});
return new DirectoryListing($listing);
}
/**
* @return Traversable<T>
*/
public function getIterator(): Traversable
{
return $this->listing instanceof Traversable
? $this->listing
: new ArrayIterator($this->listing);
}
/**
* @return T[]
*/
public function toArray(): array
{
return $this->listing instanceof Traversable
? iterator_to_array($this->listing, false)
: (array) $this->listing;
}
}

View File

@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class FileAttributes implements StorageAttributes
{
use ProxyArrayAccessToProperties;
/**
* @var string
*/
private $type = StorageAttributes::TYPE_FILE;
/**
* @var string
*/
private $path;
/**
* @var int|null
*/
private $fileSize;
/**
* @var string|null
*/
private $visibility;
/**
* @var int|null
*/
private $lastModified;
/**
* @var string|null
*/
private $mimeType;
/**
* @var array
*/
private $extraMetadata;
public function __construct(
string $path,
?int $fileSize = null,
?string $visibility = null,
?int $lastModified = null,
?string $mimeType = null,
array $extraMetadata = []
) {
$this->path = $path;
$this->fileSize = $fileSize;
$this->visibility = $visibility;
$this->lastModified = $lastModified;
$this->mimeType = $mimeType;
$this->extraMetadata = $extraMetadata;
}
public function type(): string
{
return $this->type;
}
public function path(): string
{
return $this->path;
}
public function fileSize(): ?int
{
return $this->fileSize;
}
public function visibility(): ?string
{
return $this->visibility;
}
public function lastModified(): ?int
{
return $this->lastModified;
}
public function mimeType(): ?string
{
return $this->mimeType;
}
public function extraMetadata(): array
{
return $this->extraMetadata;
}
public function isFile(): bool
{
return true;
}
public function isDir(): bool
{
return false;
}
public function withPath(string $path): StorageAttributes
{
$clone = clone $this;
$clone->path = $path;
return $clone;
}
public static function fromArray(array $attributes): StorageAttributes
{
return new FileAttributes(
$attributes[StorageAttributes::ATTRIBUTE_PATH],
$attributes[StorageAttributes::ATTRIBUTE_FILE_SIZE] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_MIME_TYPE] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
);
}
public function jsonSerialize(): array
{
return [
StorageAttributes::ATTRIBUTE_TYPE => self::TYPE_FILE,
StorageAttributes::ATTRIBUTE_PATH => $this->path,
StorageAttributes::ATTRIBUTE_FILE_SIZE => $this->fileSize,
StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
StorageAttributes::ATTRIBUTE_MIME_TYPE => $this->mimeType,
StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
];
}
}

View File

@ -1,409 +1,163 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException;
use League\Flysystem\Adapter\CanOverwriteFiles;
use League\Flysystem\Plugin\PluggableTrait;
use League\Flysystem\Util\ContentListingFormatter;
/**
* @method void emptyDir(string $dirname)
* @method array|false getWithMetadata(string $path, string[] $metadata)
* @method bool forceCopy(string $path, string $newpath)
* @method bool forceRename(string $path, string $newpath)
* @method array listFiles(string $path = '', boolean $recursive = false)
* @method string[] listPaths(string $path = '', boolean $recursive = false)
* @method array listWith(string[] $keys = [], $directory = '', $recursive = false)
*/
class Filesystem implements FilesystemInterface
class Filesystem implements FilesystemOperator
{
use PluggableTrait;
use ConfigAwareTrait;
/**
* @var FilesystemAdapter
*/
private $adapter;
/**
* @var AdapterInterface
* @var Config
*/
protected $adapter;
private $config;
/**
* Constructor.
*
* @param AdapterInterface $adapter
* @param Config|array $config
* @var PathNormalizer
*/
public function __construct(AdapterInterface $adapter, $config = null)
{
private $pathNormalizer;
public function __construct(
FilesystemAdapter $adapter,
array $config = [],
PathNormalizer $pathNormalizer = null
) {
$this->adapter = $adapter;
$this->setConfig($config);
$this->config = new Config($config);
$this->pathNormalizer = $pathNormalizer ?: new WhitespacePathNormalizer();
}
public function fileExists(string $location): bool
{
return $this->adapter->fileExists($this->pathNormalizer->normalizePath($location));
}
public function write(string $location, string $contents, array $config = []): void
{
$this->adapter->write(
$this->pathNormalizer->normalizePath($location),
$contents,
$this->config->extend($config)
);
}
public function writeStream(string $location, $contents, array $config = []): void
{
/* @var resource $contents */
$this->assertIsResource($contents);
$this->rewindStream($contents);
$this->adapter->writeStream(
$this->pathNormalizer->normalizePath($location),
$contents,
$this->config->extend($config)
);
}
public function read(string $location): string
{
return $this->adapter->read($this->pathNormalizer->normalizePath($location));
}
public function readStream(string $location)
{
return $this->adapter->readStream($this->pathNormalizer->normalizePath($location));
}
public function delete(string $location): void
{
$this->adapter->delete($this->pathNormalizer->normalizePath($location));
}
public function deleteDirectory(string $location): void
{
$this->adapter->deleteDirectory($this->pathNormalizer->normalizePath($location));
}
public function createDirectory(string $location, array $config = []): void
{
$this->adapter->createDirectory(
$this->pathNormalizer->normalizePath($location),
$this->config->extend($config)
);
}
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
{
$path = $this->pathNormalizer->normalizePath($location);
return new DirectoryListing($this->adapter->listContents($path, $deep));
}
public function move(string $source, string $destination, array $config = []): void
{
$this->adapter->move(
$this->pathNormalizer->normalizePath($source),
$this->pathNormalizer->normalizePath($destination),
$this->config->extend($config)
);
}
public function copy(string $source, string $destination, array $config = []): void
{
$this->adapter->copy(
$this->pathNormalizer->normalizePath($source),
$this->pathNormalizer->normalizePath($destination),
$this->config->extend($config)
);
}
public function lastModified(string $path): int
{
return $this->adapter->lastModified($this->pathNormalizer->normalizePath($path))->lastModified();
}
public function fileSize(string $path): int
{
return $this->adapter->fileSize($this->pathNormalizer->normalizePath($path))->fileSize();
}
public function mimeType(string $path): string
{
return $this->adapter->mimeType($this->pathNormalizer->normalizePath($path))->mimeType();
}
public function setVisibility(string $path, string $visibility): void
{
$this->adapter->setVisibility($this->pathNormalizer->normalizePath($path), $visibility);
}
public function visibility(string $path): string
{
return $this->adapter->visibility($this->pathNormalizer->normalizePath($path))->visibility();
}
/**
* Get the Adapter.
*
* @return AdapterInterface adapter
* @param mixed $contents
*/
public function getAdapter()
private function assertIsResource($contents): void
{
return $this->adapter;
}
/**
* @inheritdoc
*/
public function has($path)
{
$path = Util::normalizePath($path);
return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path);
}
/**
* @inheritdoc
*/
public function write($path, $contents, array $config = [])
{
$path = Util::normalizePath($path);
$this->assertAbsent($path);
$config = $this->prepareConfig($config);
return (bool) $this->getAdapter()->write($path, $contents, $config);
}
/**
* @inheritdoc
*/
public function writeStream($path, $resource, array $config = [])
{
if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') {
throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
}
$path = Util::normalizePath($path);
$this->assertAbsent($path);
$config = $this->prepareConfig($config);
Util::rewindStream($resource);
return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
}
/**
* @inheritdoc
*/
public function put($path, $contents, array $config = [])
{
$path = Util::normalizePath($path);
$config = $this->prepareConfig($config);
if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) {
return (bool) $this->getAdapter()->update($path, $contents, $config);
}
return (bool) $this->getAdapter()->write($path, $contents, $config);
}
/**
* @inheritdoc
*/
public function putStream($path, $resource, array $config = [])
{
if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') {
throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
}
$path = Util::normalizePath($path);
$config = $this->prepareConfig($config);
Util::rewindStream($resource);
if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) {
return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
}
return (bool) $this->getAdapter()->writeStream($path, $resource, $config);
}
/**
* @inheritdoc
*/
public function readAndDelete($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
$contents = $this->read($path);
if ($contents === false) {
return false;
}
$this->delete($path);
return $contents;
}
/**
* @inheritdoc
*/
public function update($path, $contents, array $config = [])
{
$path = Util::normalizePath($path);
$config = $this->prepareConfig($config);
$this->assertPresent($path);
return (bool) $this->getAdapter()->update($path, $contents, $config);
}
/**
* @inheritdoc
*/
public function updateStream($path, $resource, array $config = [])
{
if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') {
throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.');
}
$path = Util::normalizePath($path);
$config = $this->prepareConfig($config);
$this->assertPresent($path);
Util::rewindStream($resource);
return (bool) $this->getAdapter()->updateStream($path, $resource, $config);
}
/**
* @inheritdoc
*/
public function read($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
if ( ! ($object = $this->getAdapter()->read($path))) {
return false;
}
return $object['contents'];
}
/**
* @inheritdoc
*/
public function readStream($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
if ( ! $object = $this->getAdapter()->readStream($path)) {
return false;
}
return $object['stream'];
}
/**
* @inheritdoc
*/
public function rename($path, $newpath)
{
$path = Util::normalizePath($path);
$newpath = Util::normalizePath($newpath);
$this->assertPresent($path);
$this->assertAbsent($newpath);
return (bool) $this->getAdapter()->rename($path, $newpath);
}
/**
* @inheritdoc
*/
public function copy($path, $newpath)
{
$path = Util::normalizePath($path);
$newpath = Util::normalizePath($newpath);
$this->assertPresent($path);
$this->assertAbsent($newpath);
return $this->getAdapter()->copy($path, $newpath);
}
/**
* @inheritdoc
*/
public function delete($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
return $this->getAdapter()->delete($path);
}
/**
* @inheritdoc
*/
public function deleteDir($dirname)
{
$dirname = Util::normalizePath($dirname);
if ($dirname === '') {
throw new RootViolationException('Root directories can not be deleted.');
}
return (bool) $this->getAdapter()->deleteDir($dirname);
}
/**
* @inheritdoc
*/
public function createDir($dirname, array $config = [])
{
$dirname = Util::normalizePath($dirname);
$config = $this->prepareConfig($config);
return (bool) $this->getAdapter()->createDir($dirname, $config);
}
/**
* @inheritdoc
*/
public function listContents($directory = '', $recursive = false)
{
$directory = Util::normalizePath($directory);
$contents = $this->getAdapter()->listContents($directory, $recursive);
return (new ContentListingFormatter($directory, $recursive, $this->config->get('case_sensitive', true)))
->formatListing($contents);
}
/**
* @inheritdoc
*/
public function getMimetype($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) {
return false;
}
return $object['mimetype'];
}
/**
* @inheritdoc
*/
public function getTimestamp($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) {
return false;
}
return (int) $object['timestamp'];
}
/**
* @inheritdoc
*/
public function getVisibility($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) {
return false;
}
return $object['visibility'];
}
/**
* @inheritdoc
*/
public function getSize($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) {
return false;
}
return (int) $object['size'];
}
/**
* @inheritdoc
*/
public function setVisibility($path, $visibility)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
return (bool) $this->getAdapter()->setVisibility($path, $visibility);
}
/**
* @inheritdoc
*/
public function getMetadata($path)
{
$path = Util::normalizePath($path);
$this->assertPresent($path);
return $this->getAdapter()->getMetadata($path);
}
/**
* @inheritdoc
*/
public function get($path, Handler $handler = null)
{
$path = Util::normalizePath($path);
if ( ! $handler) {
$metadata = $this->getMetadata($path);
$handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path);
}
$handler->setPath($path);
$handler->setFilesystem($this);
return $handler;
}
/**
* Assert a file is present.
*
* @param string $path path to file
*
* @throws FileNotFoundException
*
* @return void
*/
public function assertPresent($path)
{
if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) {
throw new FileNotFoundException($path);
if (is_resource($contents) === false) {
throw new InvalidStreamProvided(
"Invalid stream provided, expected stream resource, received " . gettype($contents)
);
} elseif ($type = get_resource_type($contents) !== 'stream') {
throw new InvalidStreamProvided(
"Invalid stream provided, expected stream resource, received resource of type " . $type
);
}
}
/**
* Assert a file is absent.
*
* @param string $path path to file
*
* @throws FileExistsException
*
* @return void
* @param resource $resource
*/
public function assertAbsent($path)
private function rewindStream($resource): void
{
if ($this->config->get('disable_asserts', false) === false && $this->has($path)) {
throw new FileExistsException($path);
if (ftell($resource) !== 0 && stream_get_meta_data($resource)['seekable']) {
rewind($resource);
}
}
}

View File

@ -0,0 +1,108 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemAdapter
{
/**
* @throws FilesystemException
*/
public function fileExists(string $path): bool;
/**
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function write(string $path, string $contents, Config $config): void;
/**
* @param resource $contents
*
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function writeStream(string $path, $contents, Config $config): void;
/**
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function read(string $path): string;
/**
* @return resource
*
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function readStream(string $path);
/**
* @throws UnableToDeleteFile
* @throws FilesystemException
*/
public function delete(string $path): void;
/**
* @throws UnableToDeleteDirectory
* @throws FilesystemException
*/
public function deleteDirectory(string $path): void;
/**
* @throws UnableToCreateDirectory
* @throws FilesystemException
*/
public function createDirectory(string $path, Config $config): void;
/**
* @throws InvalidVisibilityProvided
* @throws FilesystemException
*/
public function setVisibility(string $path, string $visibility): void;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function visibility(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function mimeType(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function lastModified(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function fileSize(string $path): FileAttributes;
/**
* @return iterable<StorageAttributes>
*
* @throws FilesystemException
*/
public function listContents(string $path, bool $deep): iterable;
/**
* @throws UnableToMoveFile
* @throws FilesystemException
*/
public function move(string $source, string $destination, Config $config): void;
/**
* @throws UnableToCopyFile
* @throws FilesystemException
*/
public function copy(string $source, string $destination, Config $config): void;
}

View File

@ -1,7 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemException
use Throwable;
interface FilesystemException extends Throwable
{
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemOperationFailed extends FilesystemException
{
public const OPERATION_WRITE = 'WRITE';
public const OPERATION_UPDATE = 'UPDATE';
public const OPERATION_FILE_EXISTS = 'FILE_EXISTS';
public const OPERATION_CREATE_DIRECTORY = 'CREATE_DIRECTORY';
public const OPERATION_DELETE = 'DELETE';
public const OPERATION_DELETE_DIRECTORY = 'DELETE_DIRECTORY';
public const OPERATION_MOVE = 'MOVE';
public const OPERATION_RETRIEVE_METADATA = 'RETRIEVE_METADATA';
public const OPERATION_COPY = 'COPY';
public const OPERATION_READ = 'READ';
public const OPERATION_SET_VISIBILITY = 'SET_VISIBILITY';
public function operation(): string;
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemOperator extends FilesystemReader, FilesystemWriter
{
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
/**
* This interface contains everything to read from and inspect
* a filesystem. All methods containing are non-destructive.
*/
interface FilesystemReader
{
public const LIST_SHALLOW = false;
public const LIST_DEEP = true;
/**
* @throws FilesystemException
* @throws UnableToCheckFileExistence
*/
public function fileExists(string $location): bool;
/**
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function read(string $location): string;
/**
* @return resource
*
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function readStream(string $location);
/**
* @return DirectoryListing<StorageAttributes>
*
* @throws FilesystemException
*/
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function lastModified(string $path): int;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function fileSize(string $path): int;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function mimeType(string $path): string;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function visibility(string $path): string;
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemWriter
{
/**
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function write(string $location, string $contents, array $config = []): void;
/**
* @param mixed $contents
*
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function writeStream(string $location, $contents, array $config = []): void;
/**
* @throws UnableToSetVisibility
* @throws FilesystemException
*/
public function setVisibility(string $path, string $visibility): void;
/**
* @throws UnableToDeleteFile
* @throws FilesystemException
*/
public function delete(string $location): void;
/**
* @throws UnableToDeleteDirectory
* @throws FilesystemException
*/
public function deleteDirectory(string $location): void;
/**
* @throws UnableToCreateDirectory
* @throws FilesystemException
*/
public function createDirectory(string $location, array $config = []): void;
/**
* @throws UnableToMoveFile
* @throws FilesystemException
*/
public function move(string $source, string $destination, array $config = []): void;
/**
* @throws UnableToCopyFile
* @throws FilesystemException
*/
public function copy(string $source, string $destination, array $config = []): void;
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException as BaseInvalidArgumentException;
class InvalidStreamProvided extends BaseInvalidArgumentException implements FilesystemException
{
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException;
use function var_export;
class InvalidVisibilityProvided extends InvalidArgumentException implements FilesystemException
{
public static function withVisibility(string $visibility, string $expectedMessage): InvalidVisibilityProvided
{
$provided = var_export($visibility, true);
$message = "Invalid visibility provided. Expected {$expectedMessage}, received {$provided}";
throw new InvalidVisibilityProvided($message);
}
}

View File

@ -0,0 +1,419 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\Local;
use function file_put_contents;
use const DIRECTORY_SEPARATOR;
use const LOCK_EX;
use DirectoryIterator;
use FilesystemIterator;
use Generator;
use League\Flysystem\Config;
use League\Flysystem\DirectoryAttributes;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\PathPrefixer;
use League\Flysystem\SymbolicLinkEncountered;
use League\Flysystem\UnableToCopyFile;
use League\Flysystem\UnableToCreateDirectory;
use League\Flysystem\UnableToDeleteDirectory;
use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToMoveFile;
use League\Flysystem\UnableToReadFile;
use League\Flysystem\UnableToRetrieveMetadata;
use League\Flysystem\UnableToSetVisibility;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use League\Flysystem\UnixVisibility\VisibilityConverter;
use League\MimeTypeDetection\FinfoMimeTypeDetector;
use League\MimeTypeDetection\MimeTypeDetector;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use function chmod;
use function clearstatcache;
use function dirname;
use function error_clear_last;
use function error_get_last;
use function file_exists;
use function is_dir;
use function is_file;
use function mkdir;
use function rename;
use function stream_copy_to_stream;
class LocalFilesystemAdapter implements FilesystemAdapter
{
/**
* @var int
*/
public const SKIP_LINKS = 0001;
/**
* @var int
*/
public const DISALLOW_LINKS = 0002;
/**
* @var PathPrefixer
*/
private $prefixer;
/**
* @var int
*/
private $writeFlags;
/**
* @var int
*/
private $linkHandling;
/**
* @var VisibilityConverter
*/
private $visibility;
/**
* @var MimeTypeDetector
*/
private $mimeTypeDetector;
public function __construct(
string $location,
VisibilityConverter $visibility = null,
int $writeFlags = LOCK_EX,
int $linkHandling = self::DISALLOW_LINKS,
MimeTypeDetector $mimeTypeDetector = null
) {
$this->prefixer = new PathPrefixer($location, DIRECTORY_SEPARATOR);
$this->writeFlags = $writeFlags;
$this->linkHandling = $linkHandling;
$this->visibility = $visibility ?: new PortableVisibilityConverter();
$this->ensureDirectoryExists($location, $this->visibility->defaultForDirectories());
$this->mimeTypeDetector = $mimeTypeDetector ?: new FinfoMimeTypeDetector();
}
public function write(string $path, string $contents, Config $config): void
{
$this->writeToFile($path, $contents, $config);
}
public function writeStream(string $path, $contents, Config $config): void
{
$this->writeToFile($path, $contents, $config);
}
/**
* @param resource|string $contents
*/
private function writeToFile(string $path, $contents, Config $config): void
{
$prefixedLocation = $this->prefixer->prefixPath($path);
$this->ensureDirectoryExists(
dirname($prefixedLocation),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
error_clear_last();
if (@file_put_contents($prefixedLocation, $contents, $this->writeFlags) === false) {
throw UnableToWriteFile::atLocation($path, error_get_last()['message'] ?? '');
}
if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($path, (string) $visibility);
}
}
public function delete(string $path): void
{
$location = $this->prefixer->prefixPath($path);
if ( ! file_exists($location)) {
return;
}
error_clear_last();
if ( ! @unlink($location)) {
throw UnableToDeleteFile::atLocation($location, error_get_last()['message'] ?? '');
}
}
public function deleteDirectory(string $prefix): void
{
$location = $this->prefixer->prefixPath($prefix);
if ( ! is_dir($location)) {
return;
}
$contents = $this->listDirectoryRecursively($location, RecursiveIteratorIterator::CHILD_FIRST);
/** @var SplFileInfo $file */
foreach ($contents as $file) {
if ( ! $this->deleteFileInfoObject($file)) {
throw UnableToDeleteDirectory::atLocation($prefix, "Unable to delete file at " . $file->getPathname());
}
}
unset($contents);
if ( ! @rmdir($location)) {
throw UnableToDeleteDirectory::atLocation($prefix, error_get_last()['message'] ?? '');
}
}
private function listDirectoryRecursively(
string $path,
int $mode = RecursiveIteratorIterator::SELF_FIRST
): Generator {
yield from new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
$mode
);
}
protected function deleteFileInfoObject(SplFileInfo $file): bool
{
switch ($file->getType()) {
case 'dir':
return @rmdir((string) $file->getRealPath());
case 'link':
return @unlink((string) $file->getPathname());
default:
return @unlink((string) $file->getRealPath());
}
}
public function listContents(string $path, bool $deep): iterable
{
$location = $this->prefixer->prefixPath($path);
if ( ! is_dir($location)) {
return;
}
/** @var SplFileInfo[] $iterator */
$iterator = $deep ? $this->listDirectoryRecursively($location) : $this->listDirectory($location);
foreach ($iterator as $fileInfo) {
if ($fileInfo->isLink()) {
if ($this->linkHandling & self::SKIP_LINKS) {
continue;
}
throw SymbolicLinkEncountered::atLocation($fileInfo->getPathname());
}
$path = $this->prefixer->stripPrefix($fileInfo->getPathname());
$lastModified = $fileInfo->getMTime();
$isDirectory = $fileInfo->isDir();
$permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4));
$visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions);
yield $isDirectory ? new DirectoryAttributes($path, $visibility, $lastModified) : new FileAttributes(
str_replace('\\', '/', $path),
$fileInfo->getSize(),
$visibility,
$lastModified
);
}
}
public function move(string $source, string $destination, Config $config): void
{
$sourcePath = $this->prefixer->prefixPath($source);
$destinationPath = $this->prefixer->prefixPath($destination);
$this->ensureDirectoryExists(
dirname($destinationPath),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
if ( ! @rename($sourcePath, $destinationPath)) {
throw UnableToMoveFile::fromLocationTo($sourcePath, $destinationPath);
}
}
public function copy(string $source, string $destination, Config $config): void
{
$sourcePath = $this->prefixer->prefixPath($source);
$destinationPath = $this->prefixer->prefixPath($destination);
$this->ensureDirectoryExists(
dirname($destinationPath),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
if ( ! @copy($sourcePath, $destinationPath)) {
throw UnableToCopyFile::fromLocationTo($sourcePath, $destinationPath);
}
}
public function read(string $path): string
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$contents = @file_get_contents($location);
if ($contents === false) {
throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? '');
}
return $contents;
}
public function readStream(string $path)
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$contents = @fopen($location, 'rb');
if ($contents === false) {
throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? '');
}
return $contents;
}
protected function ensureDirectoryExists(string $dirname, int $visibility): void
{
if (is_dir($dirname)) {
return;
}
error_clear_last();
if ( ! @mkdir($dirname, $visibility, true)) {
$mkdirError = error_get_last();
}
clearstatcache(true, $dirname);
if ( ! is_dir($dirname)) {
$errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
throw UnableToCreateDirectory::atLocation($dirname, $errorMessage);
}
}
public function fileExists(string $location): bool
{
$location = $this->prefixer->prefixPath($location);
return is_file($location);
}
public function createDirectory(string $path, Config $config): void
{
$location = $this->prefixer->prefixPath($path);
$visibility = $config->get(Config::OPTION_VISIBILITY, $config->get(Config::OPTION_DIRECTORY_VISIBILITY));
$permissions = $this->resolveDirectoryVisibility($visibility);
if (is_dir($location)) {
$this->setPermissions($location, $permissions);
return;
}
error_clear_last();
if ( ! @mkdir($location, $permissions, true)) {
throw UnableToCreateDirectory::atLocation($path, error_get_last()['message'] ?? '');
}
}
public function setVisibility(string $path, string $visibility): void
{
$path = $this->prefixer->prefixPath($path);
$visibility = is_dir($path) ? $this->visibility->forDirectory($visibility) : $this->visibility->forFile(
$visibility
);
$this->setPermissions($path, $visibility);
}
public function visibility(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
clearstatcache(false, $location);
error_clear_last();
$fileperms = @fileperms($location);
if ($fileperms === false) {
throw UnableToRetrieveMetadata::visibility($path, error_get_last()['message'] ?? '');
}
$permissions = $fileperms & 0777;
$visibility = $this->visibility->inverseForFile($permissions);
return new FileAttributes($path, null, $visibility);
}
private function resolveDirectoryVisibility(?string $visibility): int
{
return $visibility === null ? $this->visibility->defaultForDirectories() : $this->visibility->forDirectory(
$visibility
);
}
public function mimeType(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$mimeType = $this->mimeTypeDetector->detectMimeTypeFromFile($location);
if ($mimeType === null) {
throw UnableToRetrieveMetadata::mimeType($path, error_get_last()['message'] ?? '');
}
return new FileAttributes($path, null, null, null, $mimeType);
}
public function lastModified(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$lastModified = @filemtime($location);
if ($lastModified === false) {
throw UnableToRetrieveMetadata::lastModified($path, error_get_last()['message'] ?? '');
}
return new FileAttributes($path, null, null, $lastModified);
}
public function fileSize(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
if (is_file($location) && ($fileSize = @filesize($location)) !== false) {
return new FileAttributes($path, $fileSize);
}
throw UnableToRetrieveMetadata::fileSize($path, error_get_last()['message'] ?? '');
}
private function listDirectory(string $location): Generator
{
$iterator = new DirectoryIterator($location);
foreach ($iterator as $item) {
if ($item->isDot()) {
continue;
}
yield $item;
}
}
private function setPermissions(string $location, int $visibility): void
{
error_clear_last();
if ( ! @chmod($location, $visibility)) {
$extraMessage = error_get_last()['message'] ?? '';
throw UnableToSetVisibility::atLocation($this->prefixer->stripPrefix($location), $extraMessage);
}
}
}

View File

@ -1,648 +1,334 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException;
use League\Flysystem\Plugin\PluggableTrait;
use League\Flysystem\Plugin\PluginNotFoundException;
use function sprintf;
/**
* Class MountManager.
*
* Proxies methods to Filesystem (@see __call):
*
* @method AdapterInterface getAdapter($prefix)
* @method Config getConfig($prefix)
* @method array listFiles($directory = '', $recursive = false)
* @method array listPaths($directory = '', $recursive = false)
* @method array getWithMetadata($path, array $metadata)
* @method Filesystem flushCache()
* @method void assertPresent($path)
* @method void assertAbsent($path)
* @method Filesystem addPlugin(PluginInterface $plugin)
*/
class MountManager implements FilesystemInterface
class MountManager implements FilesystemOperator
{
use PluggableTrait;
/**
* @var FilesystemInterface[]
* @var array<string, FilesystemOperator>
*/
protected $filesystems = [];
private $filesystems = [];
/**
* Constructor.
* MountManager constructor.
*
* @param FilesystemInterface[] $filesystems [:prefix => Filesystem,]
*
* @throws InvalidArgumentException
* @param array<string,FilesystemOperator> $filesystems
*/
public function __construct(array $filesystems = [])
{
$this->mountFilesystems($filesystems);
}
/**
* Mount filesystems.
*
* @param FilesystemInterface[] $filesystems [:prefix => Filesystem,]
*
* @throws InvalidArgumentException
*
* @return $this
*/
public function mountFilesystems(array $filesystems)
public function fileExists(string $location): bool
{
foreach ($filesystems as $prefix => $filesystem) {
$this->mountFilesystem($prefix, $filesystem);
}
return $this;
}
/**
* Mount filesystems.
*
* @param string $prefix
* @param FilesystemInterface $filesystem
*
* @throws InvalidArgumentException
*
* @return $this
*/
public function mountFilesystem($prefix, FilesystemInterface $filesystem)
{
if ( ! is_string($prefix)) {
throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.');
}
$this->filesystems[$prefix] = $filesystem;
return $this;
}
/**
* Get the filesystem with the corresponding prefix.
*
* @param string $prefix
*
* @throws FilesystemNotFoundException
*
* @return FilesystemInterface
*/
public function getFilesystem($prefix)
{
if ( ! isset($this->filesystems[$prefix])) {
throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix);
}
return $this->filesystems[$prefix];
}
/**
* Retrieve the prefix from an arguments array.
*
* @param array $arguments
*
* @throws InvalidArgumentException
*
* @return array [:prefix, :arguments]
*/
public function filterPrefix(array $arguments)
{
if (empty($arguments)) {
throw new InvalidArgumentException('At least one argument needed');
}
$path = array_shift($arguments);
if ( ! is_string($path)) {
throw new InvalidArgumentException('First argument should be a string');
}
list($prefix, $path) = $this->getPrefixAndPath($path);
array_unshift($arguments, $path);
return [$prefix, $arguments];
}
/**
* @param string $directory
* @param bool $recursive
*
* @throws InvalidArgumentException
* @throws FilesystemNotFoundException
*
* @return array
*/
public function listContents($directory = '', $recursive = false)
{
list($prefix, $directory) = $this->getPrefixAndPath($directory);
$filesystem = $this->getFilesystem($prefix);
$result = $filesystem->listContents($directory, $recursive);
foreach ($result as &$file) {
$file['filesystem'] = $prefix;
}
return $result;
}
/**
* Call forwarder.
*
* @param string $method
* @param array $arguments
*
* @throws InvalidArgumentException
* @throws FilesystemNotFoundException
*
* @return mixed
*/
public function __call($method, $arguments)
{
list($prefix, $arguments) = $this->filterPrefix($arguments);
return $this->invokePluginOnFilesystem($method, $arguments, $prefix);
}
/**
* @param string $from
* @param string $to
* @param array $config
*
* @throws InvalidArgumentException
* @throws FilesystemNotFoundException
* @throws FileExistsException
*
* @return bool
*/
public function copy($from, $to, array $config = [])
{
list($prefixFrom, $from) = $this->getPrefixAndPath($from);
$buffer = $this->getFilesystem($prefixFrom)->readStream($from);
if ($buffer === false) {
return false;
}
list($prefixTo, $to) = $this->getPrefixAndPath($to);
$result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config);
if (is_resource($buffer)) {
fclose($buffer);
}
return $result;
}
/**
* List with plugin adapter.
*
* @param array $keys
* @param string $directory
* @param bool $recursive
*
* @throws InvalidArgumentException
* @throws FilesystemNotFoundException
*
* @return array
*/
public function listWith(array $keys = [], $directory = '', $recursive = false)
{
list($prefix, $directory) = $this->getPrefixAndPath($directory);
$arguments = [$keys, $directory, $recursive];
return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix);
}
/**
* Move a file.
*
* @param string $from
* @param string $to
* @param array $config
*
* @throws InvalidArgumentException
* @throws FilesystemNotFoundException
*
* @return bool
*/
public function move($from, $to, array $config = [])
{
list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from);
list($prefixTo, $pathTo) = $this->getPrefixAndPath($to);
if ($prefixFrom === $prefixTo) {
$filesystem = $this->getFilesystem($prefixFrom);
$renamed = $filesystem->rename($pathFrom, $pathTo);
if ($renamed && isset($config['visibility'])) {
return $filesystem->setVisibility($pathTo, $config['visibility']);
}
return $renamed;
}
$copied = $this->copy($from, $to, $config);
if ($copied) {
return $this->delete($from);
}
return false;
}
/**
* Invoke a plugin on a filesystem mounted on a given prefix.
*
* @param string $method
* @param array $arguments
* @param string $prefix
*
* @throws FilesystemNotFoundException
*
* @return mixed
*/
public function invokePluginOnFilesystem($method, $arguments, $prefix)
{
$filesystem = $this->getFilesystem($prefix);
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $this->invokePlugin($method, $arguments, $filesystem);
} catch (PluginNotFoundException $e) {
// Let it pass, it's ok, don't panic.
return $filesystem->fileExists($path);
} catch (UnableToCheckFileExistence $exception) {
throw UnableToCheckFileExistence::forLocation($location, $exception);
}
}
public function read(string $location): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->read($path);
} catch (UnableToReadFile $exception) {
throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
}
}
public function readStream(string $location)
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->readStream($path);
} catch (UnableToReadFile $exception) {
throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
}
}
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path, $mountIdentifier] = $this->determineFilesystemAndPath($location);
return
$filesystem
->listContents($path, $deep)
->map(
function (StorageAttributes $attributes) use ($mountIdentifier) {
return $attributes->withPath(sprintf('%s://%s', $mountIdentifier, $attributes->path()));
}
);
}
public function lastModified(string $location): int
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->lastModified($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::lastModified($location, $exception->reason(), $exception);
}
}
public function fileSize(string $location): int
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->fileSize($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::fileSize($location, $exception->reason(), $exception);
}
}
public function mimeType(string $location): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->mimeType($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::mimeType($location, $exception->reason(), $exception);
}
}
public function visibility(string $location): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->visibility($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::visibility($location, $exception->reason(), $exception);
}
}
public function write(string $location, string $contents, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->write($path, $contents, $config);
} catch (UnableToWriteFile $exception) {
throw UnableToWriteFile::atLocation($location, $exception->reason(), $exception);
}
}
public function writeStream(string $location, $contents, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
$filesystem->writeStream($path, $contents, $config);
}
public function setVisibility(string $path, string $visibility): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($path);
$filesystem->setVisibility($path, $visibility);
}
public function delete(string $location): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->delete($path);
} catch (UnableToDeleteFile $exception) {
throw UnableToDeleteFile::atLocation($location, '', $exception);
}
}
public function deleteDirectory(string $location): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->deleteDirectory($path);
} catch (UnableToDeleteDirectory $exception) {
throw UnableToDeleteDirectory::atLocation($location, '', $exception);
}
}
public function createDirectory(string $location, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->createDirectory($path, $config);
} catch (UnableToCreateDirectory $exception) {
throw UnableToCreateDirectory::dueToFailure($location, $exception);
}
}
public function move(string $source, string $destination, array $config = []): void
{
/** @var FilesystemOperator $sourceFilesystem */
/* @var FilesystemOperator $destinationFilesystem */
[$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
[$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
$sourceFilesystem === $destinationFilesystem ? $this->moveInTheSameFilesystem(
$sourceFilesystem,
$sourcePath,
$destinationPath,
$source,
$destination
) : $this->moveAcrossFilesystems($source, $destination);
}
public function copy(string $source, string $destination, array $config = []): void
{
/** @var FilesystemOperator $sourceFilesystem */
/* @var FilesystemOperator $destinationFilesystem */
[$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
[$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
$sourceFilesystem === $destinationFilesystem ? $this->copyInSameFilesystem(
$sourceFilesystem,
$sourcePath,
$destinationPath,
$source,
$destination
) : $this->copyAcrossFilesystem(
$config['visibility'] ?? null,
$sourceFilesystem,
$sourcePath,
$destinationFilesystem,
$destinationPath,
$source,
$destination
);
}
private function mountFilesystems(array $filesystems): void
{
foreach ($filesystems as $key => $filesystem) {
$this->guardAgainstInvalidMount($key, $filesystem);
/* @var string $key */
/* @var FilesystemOperator $filesystem */
$this->mountFilesystem($key, $filesystem);
}
}
/**
* @param mixed $key
* @param mixed $filesystem
*/
private function guardAgainstInvalidMount($key, $filesystem): void
{
if ( ! is_string($key)) {
throw UnableToMountFilesystem::becauseTheKeyIsNotValid($key);
}
$callback = [$filesystem, $method];
if ( ! $filesystem instanceof FilesystemOperator) {
throw UnableToMountFilesystem::becauseTheFilesystemWasNotValid($filesystem);
}
}
return call_user_func_array($callback, $arguments);
private function mountFilesystem(string $key, FilesystemOperator $filesystem): void
{
$this->filesystems[$key] = $filesystem;
}
/**
* @param string $path
*
* @throws InvalidArgumentException
*
* @return string[] [:prefix, :path]
* @return array{0:FilesystemOperator, 1:string}
*/
protected function getPrefixAndPath($path)
private function determineFilesystemAndPath(string $path): array
{
if (strpos($path, '://') < 1) {
throw new InvalidArgumentException('No prefix detected in path: ' . $path);
throw UnableToResolveFilesystemMount::becauseTheSeparatorIsMissing($path);
}
return explode('://', $path, 2);
/** @var string $mountIdentifier */
/** @var string $mountPath */
[$mountIdentifier, $mountPath] = explode('://', $path, 2);
if ( ! array_key_exists($mountIdentifier, $this->filesystems)) {
throw UnableToResolveFilesystemMount::becauseTheMountWasNotRegistered($mountIdentifier);
}
return [$this->filesystems[$mountIdentifier], $mountPath, $mountIdentifier];
}
/**
* Check whether a file exists.
*
* @param string $path
*
* @return bool
*/
public function has($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->has($path);
private function copyInSameFilesystem(
FilesystemOperator $sourceFilesystem,
string $sourcePath,
string $destinationPath,
string $source,
string $destination
): void {
try {
$sourceFilesystem->copy($sourcePath, $destinationPath);
} catch (UnableToCopyFile $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
}
/**
* Read a file.
*
* @param string $path The path to the file.
*
* @throws FileNotFoundException
*
* @return string|false The file contents or false on failure.
*/
public function read($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->read($path);
private function copyAcrossFilesystem(
?string $visibility,
FilesystemOperator $sourceFilesystem,
string $sourcePath,
FilesystemOperator $destinationFilesystem,
string $destinationPath,
string $source,
string $destination
): void {
try {
$visibility = $visibility ?? $sourceFilesystem->visibility($sourcePath);
$stream = $sourceFilesystem->readStream($sourcePath);
$destinationFilesystem->writeStream($destinationPath, $stream, compact('visibility'));
} catch (UnableToRetrieveMetadata | UnableToReadFile | UnableToWriteFile $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
}
/**
* Retrieves a read-stream for a path.
*
* @param string $path The path to the file.
*
* @throws FileNotFoundException
*
* @return resource|false The path resource or false on failure.
*/
public function readStream($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->readStream($path);
private function moveInTheSameFilesystem(
FilesystemOperator $sourceFilesystem,
string $sourcePath,
string $destinationPath,
string $source,
string $destination
): void {
try {
$sourceFilesystem->move($sourcePath, $destinationPath);
} catch (UnableToMoveFile $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}
}
/**
* Get a file's metadata.
*
* @param string $path The path to the file.
*
* @throws FileNotFoundException
*
* @return array|false The file metadata or false on failure.
*/
public function getMetadata($path)
private function moveAcrossFilesystems(string $source, string $destination): void
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->getMetadata($path);
}
/**
* Get a file's size.
*
* @param string $path The path to the file.
*
* @throws FileNotFoundException
*
* @return int|false The file size or false on failure.
*/
public function getSize($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->getSize($path);
}
/**
* Get a file's mime-type.
*
* @param string $path The path to the file.
*
* @throws FileNotFoundException
*
* @return string|false The file mime-type or false on failure.
*/
public function getMimetype($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->getMimetype($path);
}
/**
* Get a file's timestamp.
*
* @param string $path The path to the file.
*
* @throws FileNotFoundException
*
* @return string|false The timestamp or false on failure.
*/
public function getTimestamp($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->getTimestamp($path);
}
/**
* Get a file's visibility.
*
* @param string $path The path to the file.
*
* @throws FileNotFoundException
*
* @return string|false The visibility (public|private) or false on failure.
*/
public function getVisibility($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->getVisibility($path);
}
/**
* Write a new file.
*
* @param string $path The path of the new file.
* @param string $contents The file contents.
* @param array $config An optional configuration array.
*
* @throws FileExistsException
*
* @return bool True on success, false on failure.
*/
public function write($path, $contents, array $config = [])
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->write($path, $contents, $config);
}
/**
* Write a new file using a stream.
*
* @param string $path The path of the new file.
* @param resource $resource The file handle.
* @param array $config An optional configuration array.
*
* @throws InvalidArgumentException If $resource is not a file handle.
* @throws FileExistsException
*
* @return bool True on success, false on failure.
*/
public function writeStream($path, $resource, array $config = [])
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->writeStream($path, $resource, $config);
}
/**
* Update an existing file.
*
* @param string $path The path of the existing file.
* @param string $contents The file contents.
* @param array $config An optional configuration array.
*
* @throws FileNotFoundException
*
* @return bool True on success, false on failure.
*/
public function update($path, $contents, array $config = [])
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->update($path, $contents, $config);
}
/**
* Update an existing file using a stream.
*
* @param string $path The path of the existing file.
* @param resource $resource The file handle.
* @param array $config An optional configuration array.
*
* @throws InvalidArgumentException If $resource is not a file handle.
* @throws FileNotFoundException
*
* @return bool True on success, false on failure.
*/
public function updateStream($path, $resource, array $config = [])
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->updateStream($path, $resource, $config);
}
/**
* Rename a file.
*
* @param string $path Path to the existing file.
* @param string $newpath The new path of the file.
*
* @throws FileExistsException Thrown if $newpath exists.
* @throws FileNotFoundException Thrown if $path does not exist.
*
* @return bool True on success, false on failure.
*/
public function rename($path, $newpath)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->rename($path, $newpath);
}
/**
* Delete a file.
*
* @param string $path
*
* @throws FileNotFoundException
*
* @return bool True on success, false on failure.
*/
public function delete($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->delete($path);
}
/**
* Delete a directory.
*
* @param string $dirname
*
* @throws RootViolationException Thrown if $dirname is empty.
*
* @return bool True on success, false on failure.
*/
public function deleteDir($dirname)
{
list($prefix, $dirname) = $this->getPrefixAndPath($dirname);
return $this->getFilesystem($prefix)->deleteDir($dirname);
}
/**
* Create a directory.
*
* @param string $dirname The name of the new directory.
* @param array $config An optional configuration array.
*
* @return bool True on success, false on failure.
*/
public function createDir($dirname, array $config = [])
{
list($prefix, $dirname) = $this->getPrefixAndPath($dirname);
return $this->getFilesystem($prefix)->createDir($dirname);
}
/**
* Set the visibility for a file.
*
* @param string $path The path to the file.
* @param string $visibility One of 'public' or 'private'.
*
* @throws FileNotFoundException
*
* @return bool True on success, false on failure.
*/
public function setVisibility($path, $visibility)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->setVisibility($path, $visibility);
}
/**
* Create a file or update if exists.
*
* @param string $path The path to the file.
* @param string $contents The file contents.
* @param array $config An optional configuration array.
*
* @return bool True on success, false on failure.
*/
public function put($path, $contents, array $config = [])
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->put($path, $contents, $config);
}
/**
* Create a file or update if exists.
*
* @param string $path The path to the file.
* @param resource $resource The file handle.
* @param array $config An optional configuration array.
*
* @throws InvalidArgumentException Thrown if $resource is not a resource.
*
* @return bool True on success, false on failure.
*/
public function putStream($path, $resource, array $config = [])
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->putStream($path, $resource, $config);
}
/**
* Read and delete a file.
*
* @param string $path The path to the file.
*
* @throws FileNotFoundException
*
* @return string|false The file contents, or false on failure.
*/
public function readAndDelete($path)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->readAndDelete($path);
}
/**
* Get a file/directory handler.
*
* @deprecated
*
* @param string $path The path to the file.
* @param Handler $handler An optional existing handler to populate.
*
* @return Handler Either a file or directory handler.
*/
public function get($path, Handler $handler = null)
{
list($prefix, $path) = $this->getPrefixAndPath($path);
return $this->getFilesystem($prefix)->get($path);
try {
$this->copy($source, $destination);
$this->delete($source);
} catch (UnableToCopyFile | UnableToDeleteFile $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface PathNormalizer
{
public function normalizePath(string $path): string;
}

View File

@ -0,0 +1,60 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use function rtrim;
use function strlen;
use function substr;
final class PathPrefixer
{
/**
* @var string
*/
private $prefix = '';
/**
* @var string
*/
private $separator = '/';
public function __construct(string $prefix, string $separator = '/')
{
$this->prefix = rtrim($prefix, '\\/');
if ($this->prefix !== '' || $prefix === $separator) {
$this->prefix .= $separator;
}
$this->separator = $separator;
}
public function prefixPath(string $path): string
{
return $this->prefix . ltrim($path, '\\/');
}
public function stripPrefix(string $path): string
{
/* @var string */
return substr($path, strlen($this->prefix));
}
public function stripDirectoryPrefix(string $path): string
{
return rtrim($this->stripPrefix($path), '\\/');
}
public function prefixDirectoryPath(string $path): string
{
$prefixedPath = $this->prefixPath(rtrim($path, '\\/'));
if ((substr($prefixedPath, -1) === $this->separator) || $prefixedPath === '') {
return $prefixedPath;
}
return $prefixedPath . $this->separator;
}
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
class PathTraversalDetected extends RuntimeException implements FilesystemException
{
/**
* @var string
*/
private $path;
public function path(): string
{
return $this->path;
}
public static function forPath(string $path): PathTraversalDetected
{
$e = new PathTraversalDetected("Path traversal detected: {$path}");
$e->path = $path;
return $e;
}
}

View File

@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
final class PortableVisibilityGuard
{
public static function guardAgainstInvalidInput(string $visibility): void
{
if ($visibility !== Visibility::PUBLIC && $visibility !== Visibility::PRIVATE) {
$className = Visibility::class;
throw InvalidVisibilityProvided::withVisibility(
$visibility,
"either {$className}::PUBLIC or {$className}::PRIVATE"
);
}
}
}

View File

@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
/**
* @internal
*/
trait ProxyArrayAccessToProperties
{
private function formatPropertyName(string $offset): string
{
return str_replace('_', '', lcfirst(ucwords($offset, '_')));
}
/**
* @param mixed $offset
*
* @return bool
*/
public function offsetExists($offset): bool
{
$property = $this->formatPropertyName((string) $offset);
return isset($this->{$property});
}
/**
* @param mixed $offset
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
$property = $this->formatPropertyName((string) $offset);
return $this->{$property};
}
/**
* @param mixed $offset
* @param mixed $value
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value): void
{
throw new RuntimeException('Properties can not be manipulated');
}
/**
* @param mixed $offset
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset): void
{
throw new RuntimeException('Properties can not be manipulated');
}
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use ArrayAccess;
use JsonSerializable;
interface StorageAttributes extends JsonSerializable, ArrayAccess
{
public const ATTRIBUTE_PATH = 'path';
public const ATTRIBUTE_TYPE = 'type';
public const ATTRIBUTE_FILE_SIZE = 'file_size';
public const ATTRIBUTE_VISIBILITY = 'visibility';
public const ATTRIBUTE_LAST_MODIFIED = 'last_modified';
public const ATTRIBUTE_MIME_TYPE = 'mime_type';
public const ATTRIBUTE_EXTRA_METADATA = 'extra_metadata';
public const TYPE_FILE = 'file';
public const TYPE_DIRECTORY = 'dir';
public function path(): string;
public function type(): string;
public function visibility(): ?string;
public function lastModified(): ?int;
public static function fromArray(array $attributes): StorageAttributes;
public function isFile(): bool;
public function isDir(): bool;
public function withPath(string $path): StorageAttributes;
public function extraMetadata(): array;
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
final class SymbolicLinkEncountered extends RuntimeException implements FilesystemException
{
/**
* @var string
*/
private $location;
public function location(): string
{
return $this->location;
}
public static function atLocation(string $pathName): SymbolicLinkEncountered
{
$e = new static("Unsupported symbolic link encountered at location $pathName");
$e->location = $pathName;
return $e;
}
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
class UnableToCheckFileExistence extends RuntimeException implements FilesystemOperationFailed
{
public static function forLocation(string $path, Throwable $exception = null): UnableToCheckFileExistence
{
return new UnableToCheckFileExistence("Unable to check file existence for: ${path}", 0, $exception);
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_FILE_EXISTS;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToCopyFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $source;
/**
* @var string
*/
private $destination;
public function source(): string
{
return $this->source;
}
public function destination(): string
{
return $this->destination;
}
public static function fromLocationTo(
string $sourcePath,
string $destinationPath,
Throwable $previous = null
): UnableToCopyFile {
$e = new static("Unable to copy file from $sourcePath to $destinationPath", 0 , $previous);
$e->source = $sourcePath;
$e->destination = $destinationPath;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_COPY;
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToCreateDirectory extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location;
public static function atLocation(string $dirname, string $errorMessage = ''): UnableToCreateDirectory
{
$message = "Unable to create a directory at {$dirname}. ${errorMessage}";
$e = new static(rtrim($message));
$e->location = $dirname;
return $e;
}
public static function dueToFailure(string $dirname, Throwable $previous): UnableToCreateDirectory
{
$message = "Unable to create a directory at {$dirname}";
$e = new static($message, 0, $previous);
$e->location = $dirname;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_CREATE_DIRECTORY;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToDeleteDirectory extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location = '';
/**
* @var string
*/
private $reason;
public static function atLocation(
string $location,
string $reason = '',
Throwable $previous = null
): UnableToDeleteDirectory {
$e = new static(rtrim("Unable to delete directory located at: {$location}. {$reason}"), 0, $previous);
$e->location = $location;
$e->reason = $reason;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_DELETE_DIRECTORY;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToDeleteFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location = '';
/**
* @var string
*/
private $reason;
public static function atLocation(string $location, string $reason = '', Throwable $previous = null): UnableToDeleteFile
{
$e = new static(rtrim("Unable to delete file located at: {$location}. {$reason}"), 0, $previous);
$e->location = $location;
$e->reason = $reason;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_DELETE;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use LogicException;
class UnableToMountFilesystem extends LogicException implements FilesystemException
{
/**
* @param mixed $key
*/
public static function becauseTheKeyIsNotValid($key): UnableToMountFilesystem
{
return new UnableToMountFilesystem(
'Unable to mount filesystem, key was invalid. String expected, received: ' . gettype($key)
);
}
/**
* @param mixed $filesystem
*/
public static function becauseTheFilesystemWasNotValid($filesystem): UnableToMountFilesystem
{
$received = is_object($filesystem) ? get_class($filesystem) : gettype($filesystem);
return new UnableToMountFilesystem(
'Unable to mount filesystem, filesystem was invalid. Instance of ' . FilesystemOperator::class . ' expected, received: ' . $received
);
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToMoveFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $source;
/**
* @var string
*/
private $destination;
public function source(): string
{
return $this->source;
}
public function destination(): string
{
return $this->destination;
}
public static function fromLocationTo(
string $sourcePath,
string $destinationPath,
Throwable $previous = null
): UnableToMoveFile {
$e = new static("Unable to move file from $sourcePath to $destinationPath", 0, $previous);
$e->source = $sourcePath;
$e->destination = $destinationPath;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_MOVE;
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToReadFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location = '';
/**
* @var string
*/
private $reason = '';
public static function fromLocation(string $location, string $reason = '', Throwable $previous = null): UnableToReadFile
{
$e = new static(rtrim("Unable to read file from location: {$location}. {$reason}"), 0, $previous);
$e->location = $location;
$e->reason = $reason;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_READ;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
class UnableToResolveFilesystemMount extends RuntimeException implements FilesystemException
{
public static function becauseTheSeparatorIsMissing(string $path): UnableToResolveFilesystemMount
{
return new UnableToResolveFilesystemMount("Unable to resolve the filesystem mount because the path ($path) is missing a separator (://).");
}
public static function becauseTheMountWasNotRegistered(string $mountIdentifier): UnableToResolveFilesystemMount
{
return new UnableToResolveFilesystemMount("Unable to resolve the filesystem mount because the mount ($mountIdentifier) was not registered.");
}
}

View File

@ -0,0 +1,76 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToRetrieveMetadata extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location;
/**
* @var string
*/
private $metadataType;
/**
* @var string
*/
private $reason;
public static function lastModified(string $location, string $reason = '', Throwable $previous = null): self
{
return static::create($location, FileAttributes::ATTRIBUTE_LAST_MODIFIED, $reason, $previous);
}
public static function visibility(string $location, string $reason = '', Throwable $previous = null): self
{
return static::create($location, FileAttributes::ATTRIBUTE_VISIBILITY, $reason, $previous);
}
public static function fileSize(string $location, string $reason = '', Throwable $previous = null): self
{
return static::create($location, FileAttributes::ATTRIBUTE_FILE_SIZE, $reason, $previous);
}
public static function mimeType(string $location, string $reason = '', Throwable $previous = null): self
{
return static::create($location, FileAttributes::ATTRIBUTE_MIME_TYPE, $reason, $previous);
}
public static function create(string $location, string $type, string $reason = '', Throwable $previous = null): self
{
$e = new static("Unable to retrieve the $type for file at location: $location. {$reason}", 0, $previous);
$e->reason = $reason;
$e->location = $location;
$e->metadataType = $type;
return $e;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
public function metadataType(): string
{
return $this->metadataType;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_RETRIEVE_METADATA;
}
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
use function rtrim;
final class UnableToSetVisibility extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location;
/**
* @var string
*/
private $reason;
public function reason(): string
{
return $this->reason;
}
public static function atLocation(string $filename, string $extraMessage = '', Throwable $previous = null): self
{
$message = "Unable to set visibility for file {$filename}. $extraMessage";
$e = new static(rtrim($message), 0, $previous);
$e->reason = $extraMessage;
$e->location = $filename;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_SET_VISIBILITY;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
use Throwable;
final class UnableToWriteFile extends RuntimeException implements FilesystemOperationFailed
{
/**
* @var string
*/
private $location = '';
/**
* @var string
*/
private $reason;
public static function atLocation(string $location, string $reason = '', Throwable $previous = null): UnableToWriteFile
{
$e = new static(rtrim("Unable to write file at location: {$location}. {$reason}"), 0, $previous);
$e->location = $location;
$e->reason = $reason;
return $e;
}
public function operation(): string
{
return FilesystemOperationFailed::OPERATION_WRITE;
}
public function reason(): string
{
return $this->reason;
}
public function location(): string
{
return $this->location;
}
}

View File

@ -0,0 +1,109 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\UnixVisibility;
use League\Flysystem\PortableVisibilityGuard;
use League\Flysystem\Visibility;
class PortableVisibilityConverter implements VisibilityConverter
{
/**
* @var int
*/
private $filePublic;
/**
* @var int
*/
private $filePrivate;
/**
* @var int
*/
private $directoryPublic;
/**
* @var int
*/
private $directoryPrivate;
/**
* @var string
*/
private $defaultForDirectories;
public function __construct(
int $filePublic = 0644,
int $filePrivate = 0600,
int $directoryPublic = 0755,
int $directoryPrivate = 0700,
string $defaultForDirectories = Visibility::PRIVATE
) {
$this->filePublic = $filePublic;
$this->filePrivate = $filePrivate;
$this->directoryPublic = $directoryPublic;
$this->directoryPrivate = $directoryPrivate;
$this->defaultForDirectories = $defaultForDirectories;
}
public function forFile(string $visibility): int
{
PortableVisibilityGuard::guardAgainstInvalidInput($visibility);
return $visibility === Visibility::PUBLIC
? $this->filePublic
: $this->filePrivate;
}
public function forDirectory(string $visibility): int
{
PortableVisibilityGuard::guardAgainstInvalidInput($visibility);
return $visibility === Visibility::PUBLIC
? $this->directoryPublic
: $this->directoryPrivate;
}
public function inverseForFile(int $visibility): string
{
if ($visibility === $this->filePublic) {
return Visibility::PUBLIC;
} elseif ($visibility === $this->filePrivate) {
return Visibility::PRIVATE;
}
return Visibility::PUBLIC; // default
}
public function inverseForDirectory(int $visibility): string
{
if ($visibility === $this->directoryPublic) {
return Visibility::PUBLIC;
} elseif ($visibility === $this->directoryPrivate) {
return Visibility::PRIVATE;
}
return Visibility::PUBLIC; // default
}
public function defaultForDirectories(): int
{
return $this->defaultForDirectories === Visibility::PUBLIC ? $this->directoryPublic : $this->directoryPrivate;
}
/**
* @param array<mixed> $permissionMap
*/
public static function fromArray(array $permissionMap, string $defaultForDirectories = Visibility::PRIVATE): PortableVisibilityConverter
{
return new PortableVisibilityConverter(
$permissionMap['file']['public'] ?? 0644,
$permissionMap['file']['private'] ?? 0600,
$permissionMap['dir']['public'] ?? 0755,
$permissionMap['dir']['private'] ?? 0700,
$defaultForDirectories
);
}
}

View File

@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\UnixVisibility;
interface VisibilityConverter
{
public function forFile(string $visibility): int;
public function forDirectory(string $visibility): int;
public function inverseForFile(int $visibility): string;
public function inverseForDirectory(int $visibility): string;
public function defaultForDirectories(): int;
}

View File

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use RuntimeException;
final class UnreadableFileEncountered extends RuntimeException implements FilesystemException
{
/**
* @var string
*/
private $location;
public function location(): string
{
return $this->location;
}
public static function atLocation(string $location): UnreadableFileEncountered
{
$e = new static("Unreadable file encountered at location {$location}.");
$e->location = $location;
return $e;
}
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
final class Visibility
{
public const PUBLIC = 'public';
public const PRIVATE = 'private';
}

View File

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class WhitespacePathNormalizer implements PathNormalizer
{
public function normalizePath(string $path): string
{
$path = str_replace('\\', '/', $path);
$this->rejectFunkyWhiteSpace($path);
return $this->normalizeRelativePath($path);
}
private function rejectFunkyWhiteSpace(string $path): void
{
if (preg_match('#\p{C}+#u', $path)) {
throw CorruptedPathDetected::forPath($path);
}
}
private function normalizeRelativePath(string $path): string
{
$parts = [];
foreach (explode('/', $path) as $part) {
switch ($part) {
case '':
case '.':
break;
case '..':
if (empty($parts)) {
throw PathTraversalDetected::forPath($path);
}
array_pop($parts);
break;
default:
$parts[] = $part;
break;
}
}
return implode('/', $parts);
}
}

2
vendor/services.php vendored
View File

@ -1,5 +1,5 @@
<?php
// This file is automatically generated at:2022-10-30 14:42:32
// This file is automatically generated at:2022-11-07 13:45:48
declare (strict_types = 1);
return array (
0 => 'taoser\\addons\\Service',

View File

@ -0,0 +1,3 @@
composer.lock
/.idea
/vendor

View File

@ -0,0 +1,5 @@
# think-filesystem for ThinkPHP6.1
## 安装
> composer require topthink/think-filesystem

View File

@ -0,0 +1,33 @@
{
"name": "topthink/think-filesystem",
"description": "The ThinkPHP6.1 Filesystem Package",
"type": "library",
"license": "Apache-2.0",
"authors": [
{
"name": "yunwuxin",
"email": "448901948@qq.com"
}
],
"autoload": {
"psr-4": {
"think\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"think\\tests\\": "tests/"
}
},
"require": {
"topthink/framework": "^6.1",
"league/flysystem": "^2.0"
},
"require-dev": {
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2",
"phpunit/phpunit": "^8.0"
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
beStrictAboutTestsThatDoNotTestAnything="false"
bootstrap="tests/bootstrap.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
>
<testsuites>
<testsuite name="ThinkPHP Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./src/think</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -0,0 +1,89 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think;
use InvalidArgumentException;
use think\filesystem\Driver;
use think\filesystem\driver\Local;
use think\helper\Arr;
/**
* Class Filesystem
* @package think
* @mixin Driver
* @mixin Local
*/
class Filesystem extends Manager
{
protected $namespace = '\\think\\filesystem\\driver\\';
/**
* @param null|string $name
* @return Driver
*/
public function disk(string $name = null): Driver
{
return $this->driver($name);
}
protected function resolveType(string $name)
{
return $this->getDiskConfig($name, 'type', 'local');
}
protected function resolveConfig(string $name)
{
return $this->getDiskConfig($name);
}
/**
* 获取缓存配置
* @access public
* @param null|string $name 名称
* @param mixed $default 默认值
* @return mixed
*/
public function getConfig(string $name = null, $default = null)
{
if (!is_null($name)) {
return $this->app->config->get('filesystem.' . $name, $default);
}
return $this->app->config->get('filesystem');
}
/**
* 获取磁盘配置
* @param string $disk
* @param null $name
* @param null $default
* @return array
*/
public function getDiskConfig($disk, $name = null, $default = null)
{
if ($config = $this->getConfig("disks.{$disk}")) {
return Arr::get($config, $name, $default);
}
throw new InvalidArgumentException("Disk [$disk] not found.");
}
/**
* 默认驱动
* @return string|null
*/
public function getDefaultDriver()
{
return $this->getConfig('default');
}
}

View File

@ -0,0 +1,33 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\facade;
use think\Facade;
use think\filesystem\Driver;
/**
* Class Filesystem
* @package think\facade
* @mixin \think\Filesystem
* @method static Driver disk(string $name = null) , null|string
* @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置
* @method static array getDiskConfig(string $disk, null $name = null, null $default = null) 获取磁盘配置
* @method static string|null getDefaultDriver() 默认驱动
*/
class Filesystem extends Facade
{
protected static function getFacadeClass()
{
return \think\Filesystem::class;
}
}

View File

@ -0,0 +1,130 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\filesystem;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\UnableToSetVisibility;
use League\Flysystem\UnableToWriteFile;
use RuntimeException;
use think\Cache;
use think\File;
/**
* Class Driver
* @package think\filesystem
* @mixin Filesystem
*/
abstract class Driver
{
/** @var Cache */
protected $cache;
/** @var Filesystem */
protected $filesystem;
/**
* 配置参数
* @var array
*/
protected $config = [];
public function __construct(Cache $cache, array $config)
{
$this->cache = $cache;
$this->config = array_merge($this->config, $config);
$adapter = $this->createAdapter();
$this->filesystem = $this->createFilesystem($adapter);
}
abstract protected function createAdapter(): FilesystemAdapter;
protected function createFilesystem(FilesystemAdapter $adapter): Filesystem
{
$config = array_intersect_key($this->config, array_flip(['visibility', 'disable_asserts', 'url']));
return new Filesystem($adapter, count($config) > 0 ? $config : null);
}
/**
* 获取文件完整路径
* @param string $path
* @return string
*/
public function path(string $path): string
{
return $path;
}
protected function concatPathToUrl($url, $path)
{
return rtrim($url, '/') . '/' . ltrim($path, '/');
}
public function url(string $path): string
{
throw new RuntimeException('This driver does not support retrieving URLs.');
}
/**
* 保存文件
* @param string $path 路径
* @param File $file 文件
* @param null|string|\Closure $rule 文件名规则
* @param array $options 参数
* @return bool|string
*/
public function putFile(string $path, File $file, $rule = null, array $options = [])
{
return $this->putFileAs($path, $file, $file->hashName($rule), $options);
}
/**
* 指定文件名保存文件
* @param string $path 路径
* @param File $file 文件
* @param string $name 文件名
* @param array $options 参数
* @return bool|string
*/
public function putFileAs(string $path, File $file, string $name, array $options = [])
{
$stream = fopen($file->getRealPath(), 'r');
$path = trim($path . '/' . $name, '/');
$result = $this->put($path, $stream, $options);
if (is_resource($stream)) {
fclose($stream);
}
return $result ? $path : false;
}
protected function put(string $path, $contents, array $options = [])
{
try {
$this->writeStream($path, $contents, $options);
} catch (UnableToWriteFile|UnableToSetVisibility $e) {
return false;
}
return true;
}
public function __call($method, $parameters)
{
return $this->filesystem->$method(...$parameters);
}
}

View File

@ -0,0 +1,98 @@
<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006~2021 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: yunwuxin <448901948@qq.com>
// +----------------------------------------------------------------------
declare (strict_types = 1);
namespace think\filesystem\driver;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\Local\LocalFilesystemAdapter;
use League\Flysystem\PathNormalizer;
use League\Flysystem\PathPrefixer;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use League\Flysystem\Visibility;
use League\Flysystem\WhitespacePathNormalizer;
use think\filesystem\Driver;
class Local extends Driver
{
/**
* 配置参数
* @var array
*/
protected $config = [
'root' => '',
];
/**
* @var PathPrefixer
*/
protected $prefixer;
/**
* @var PathNormalizer
*/
protected $normalizer;
protected function createAdapter(): FilesystemAdapter
{
$visibility = PortableVisibilityConverter::fromArray(
$this->config['permissions'] ?? [],
$this->config['visibility'] ?? Visibility::PRIVATE
);
$links = ($this->config['links'] ?? null) === 'skip'
? LocalFilesystemAdapter::SKIP_LINKS
: LocalFilesystemAdapter::DISALLOW_LINKS;
return new LocalFilesystemAdapter(
$this->config['root'],
$visibility,
$this->config['lock'] ?? LOCK_EX,
$links
);
}
protected function prefixer()
{
if (!$this->prefixer) {
$this->prefixer = new PathPrefixer($this->config['root'], DIRECTORY_SEPARATOR);
}
return $this->prefixer;
}
protected function normalizer()
{
if (!$this->normalizer) {
$this->normalizer = new WhitespacePathNormalizer();
}
return $this->normalizer;
}
/**
* 获取文件访问地址
* @param string $path 文件路径
* @return string
*/
public function url(string $path): string
{
$path = $this->normalizer()->normalizePath($path);
if (isset($this->config['url'])) {
return $this->concatPathToUrl($this->config['url'], $path);
}
return parent::url($path);
}
public function path(string $path): string
{
return $this->prefixer()->prefixPath($path);
}
}

View File

@ -0,0 +1,66 @@
<?php
namespace think\tests;
use Mockery as m;
use Mockery\MockInterface;
use org\bovigo\vfs\vfsStream;
use org\bovigo\vfs\vfsStreamDirectory;
use PHPUnit\Framework\TestCase;
use think\App;
use think\Config;
use think\Container;
use think\Filesystem;
use think\filesystem\driver\Local;
class FilesystemTest extends TestCase
{
/** @var App|MockInterface */
protected $app;
/** @var Filesystem */
protected $filesystem;
/** @var Config|MockInterface */
protected $config;
/** @var vfsStreamDirectory */
protected $root;
protected function setUp(): void
{
$this->app = m::mock(App::class)->makePartial();
Container::setInstance($this->app);
$this->app->shouldReceive('make')->with(App::class)->andReturn($this->app);
$this->config = m::mock(Config::class);
$this->config->shouldReceive('get')->with('filesystem.default', null)->andReturn('local');
$this->app->shouldReceive('get')->with('config')->andReturn($this->config);
$this->filesystem = new Filesystem($this->app);
$this->root = vfsStream::setup('rootDir');
}
protected function tearDown(): void
{
m::close();
}
public function testDisk()
{
$this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([
'type' => 'local',
'root' => $this->root->url(),
]);
$this->config->shouldReceive('get')->with('filesystem.disks.foo', null)->andReturn([
'type' => 'local',
'root' => $this->root->url(),
]);
$this->assertInstanceOf(Local::class, $this->filesystem->disk());
$this->assertInstanceOf(Local::class, $this->filesystem->disk('foo'));
}
}

View File

@ -0,0 +1,2 @@
<?php