getPublicPath($uploadType, $addPath);
$storagePath = $this->getStoragePath($uploadType, $addPath);
// 해당 경로가 없다면 생성 해주고 index.php 파일 생성
if (is_dir($storagePath) === false) {
mkdir($storagePath, 0707, true);
touch($storagePath . '/index.php');
chmod($storagePath . '/index.php', 0644);
}
return $uploadPath;
}
/*
* Public 저장 경로 조회
*/
public function getPublicPath(string $uploadType, string|null $addPath = null): string
{
return (join(DIRECTORY_SEPARATOR, [
UPLOAD_PATH_PUBLIC,
$uploadType,
date('Y'),
date('m'),
date('d'),
$addPath
]) . DIRECTORY_SEPARATOR);
}
/*
* Storage 저장 경로 조회
*/
public function getStoragePath(string $uploadType, string|null $addPath = null): string
{
return storage_path(UPLOAD_PATH_APP . DIRECTORY_SEPARATOR . $this->getPublicPath($uploadType, $addPath));
}
/*
* 경로 완전 삭제
*/
public function removePath(string $uploadType, string|null $addPath = null): void
{
$storagePath = $this->getStoragePath($uploadType, $addPath);
if(is_dir($storagePath)) {
foreach (scandir($storagePath) as $file) {
if (in_array($file, ['.', '..'])) {
continue;
}
$path = ($storagePath . DIRECTORY_SEPARATOR . $file);
if(is_dir($path)) {
$this->removePath($uploadType, $addPath . DIRECTORY_SEPARATOR . $file);
}else{
unlink($path);
}
}
rmdir($storagePath);
}
}
/*
* 본문 내용중 외부 이미지 주소를 서버로 가져온 후에 내부 주소로 변경합니다.
*/
public function saveAsImage(string|null $contents, string $uploadType, string|null $addPath = null): string|null
{
return $this->saveImageFromBlob(
$this->saveImageFromUrl($contents, $uploadType, $addPath), $uploadType, $addPath);
}
/*
* CURL를 이용한 외부 이미지 다운로드
*/
public function saveImageFromUrl(string|null $contents, string $uploadType, string|null $addPath = null): string|null
{
$dom = new DOMDocument('1.0', 'UTF-8');
libxml_use_internal_errors(true);
$dom->loadHTML('' . $contents);
libxml_clear_errors();
$images = [];
foreach ($dom->getElementsByTagName('img') as $img)
{
$src = $img->attributes->getNamedItem("src")->value;
$path = canonicalizePath($src);
if(file_exists(public_path($path))) {
$images[] = $path;
continue;
}
if(filter_var($src, FILTER_VALIDATE_URL)) {
$url = parse_url($src);
if (!empty($url['host']) && $url['host'] !== request()->getHttpHost())
{
$ch = curl_init($src);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$err = curl_error($ch);
$rawData = [];
if (empty($err)) {
$rawData = curl_exec($ch);
}
curl_close($ch);
if ($rawData)
{
// 파일 경로 설정
$uploadPath = $this->setPath($uploadType, $addPath);
$ext = $this->getExtension($src);
[$unix, $sec] = explode(' ', microtime());
$fileName = (md5(uniqid(mt_rand())) . '_' . str_replace('.', '', $sec . $unix) . '.' . $ext);
$savePath = Storage::path($uploadPath . $fileName);
$saveURL = Storage::url($uploadPath . $fileName);
$fp = fopen($savePath, 'w+');
fwrite($fp, $rawData);
fclose($fp);
if (file_exists($savePath)) {
$images[] = $saveURL;
$contents = str_replace($src, $saveURL, $contents);
}
}
}
}
}
// 기존 이미지 삭제
$this->clear($images, $uploadType, $addPath);
unset($dom, $images, $uploadPath, $addPath);
return $contents;
}
/*
* blob 종류의 이미지 저장
*/
public function saveImageFromBlob(string|null $contents, string $uploadType, string|null $addPath = null): string|null
{
$dom = new DOMDocument('1.0', 'UTF-8');
libxml_use_internal_errors(true);
$dom->loadHTML('' . $contents);
libxml_clear_errors();
$images = [];
foreach ($dom->getElementsByTagName('img') as $img)
{
$src = $img->attributes->getNamedItem("src")?->value;
$alt = $img->attributes->getNamedItem("alt")?->value;
$target = $img->attributes->getNamedItem("data-target")?->value;
$path = canonicalizePath($src);
if(file_exists(public_path($path)) || !$target || !strpos($src, 'base64')) {
$images[] = $path;
continue;
}
try {
$mime = mime_content_type($src);
$ext = $this->getMimeExtension($mime);
if (!$ext) {
$ext = $this->getExtension($alt);
}
} catch (Exception) {
return null;
}
// 파일 경로 설정
$uploadPath = $this->setPath($uploadType, $addPath);
$link = str_replace('data:' . $mime . ';base64,', '', $src);
$link = str_replace(' ', '+', $link);
$isBase64 = isBase64($link);
$link = base64_decode($link);
[$unix, $sec] = explode(' ', microtime());
$fileName = (md5(uniqid(mt_rand())) . '_' . str_replace('.', '', $sec . $unix) . '.' . $ext);
$savePath = Storage::path($uploadPath . $fileName);
$saveUrl = Storage::url($uploadPath . $fileName);
// 파일저장
if ($isBase64) {
file_put_contents($savePath, $link, FILE_APPEND | LOCK_EX);
}
if (file_exists($savePath)) {
$images[] = $saveUrl;
$contents = str_replace($src, $saveUrl, $contents);
}
}
// 기존 이미지 삭제
$this->clear($images, $uploadType, $addPath);
unset($dom, $images, $uploadPath, $addPath);
return $contents;
}
public function isBase64($s): bool
{
// Check if there are valid base64 characters
if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $s)) {
return false;
}
// Decode the string in strict mode and check the results
$decoded = base64_decode($s, true);
if (false === $decoded) {
return false;
}
// if string returned contains not printable chars
if (0 < preg_match('/((?![[:graph:]])(?!\s)(?!\p{L}))./', $decoded, $matched)) {
return false;
}
// Encode the string again
if (base64_encode($decoded) != $s) {
return false;
}
return true;
}
public function isBase64Encoded(string $s): bool
{
if ((bool)preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $s) === false) {
return false;
}
$decoded = base64_decode($s, true);
if ($decoded === false) {
return false;
}
$encoding = mb_detect_encoding($decoded);
if (!in_array($encoding, ['UTF-8', 'ASCII'], true)) {
return false;
}
return ($decoded !== false && base64_encode($decoded) === $s);
}
/*
* 파일 확장자 조회
*/
public function getExtension(string $path): string
{
return pathinfo($path, PATHINFO_EXTENSION) ?? 'unknown';
}
/*
* 이미지 파일 여부
*/
public function isImage(string $path): bool
{
if(!file_exists($path)) {
return false;
}
$mime = mime_content_type($path);
if($mime) {
return in_array($mime, [
image_type_to_mime_type(IMAGETYPE_GIF),
image_type_to_mime_type(IMAGETYPE_JPEG),
image_type_to_mime_type(IMAGETYPE_PNG),
image_type_to_mime_type(IMAGETYPE_BMP)]
);
}
return false;
}
/*
* 첨부파일 정보 조회
*/
public function upload(UploadedFile $file, string $uploadType, string|null $addPath = null): object
{
$publicPath = $this->getPublicPath($uploadType, $addPath); // 파일 경로 조회
$filePath = $file->store($publicPath); // 파일 저장
$fullPath = Storage::path($filePath);
$fullURL = Storage::url($filePath);
$imageInfo = $this->getImageInfo($fullPath);
return (object)[
'name' => $file->hashName(),
'path' => $fullPath,
'url' => $fullURL,
'originName' => $file->getClientOriginalName(),
'extension' => $file->getClientOriginalExtension(),
'size' => $file->getSize(),
'isImage' => $imageInfo->answer,
'imageWidth' => $imageInfo->width,
'imageHeight' => $imageInfo->height,
'imageType' => $imageInfo->type
];
}
/*
* 이미지 크기 조정
*/
public function resize(string $filePath, int $width, int $height): void
{
[$w, $h, $ext] = getimagesize($filePath);
$ratio = max($width / $w, $height / $h);
$h = ceil($height / $ratio);
$x = ($w - $width / $ratio) / 2;
$w = ceil($width / $ratio);
$imgString = file_get_contents($filePath);
$image = imagecreatefromstring($imgString);
$tmp = imagecreatetruecolor($width, $height);
imagecopyresampled($tmp, $image, 0, 0, $x, 0, $width, $height, $w, $h);
switch ($ext) {
case IMAGETYPE_JPEG:
imagejpeg($tmp, $filePath, 100);
break;
case IMAGETYPE_PNG:
imagepng($tmp, $filePath, 0);
break;
case IMAGETYPE_GIF:
imagegif($tmp, $filePath);
break;
default:
break;
}
imagedestroy($image);
imagedestroy($tmp);
}
/*
* mime_content_type 값의 확장자 반환
*/
public function getMimeExtension(string $imageType): string
{
return ([
'image/bmp' => 'bmp',
'image/cis-cod' => 'cod',
'image/gif' => 'gif',
'image/ief' => 'ief',
'image/jpeg' => 'jpeg',
'image/pipeg' => 'jfif',
'image/tiff' => 'tif',
'image/x-cmu-raster' => 'ras',
'image/x-cmx' => 'cmx',
'image/x-icon' => 'ico',
'image/x-portable-anymap' => 'pnm',
'image/x-portable-bitmap' => 'pbm',
'image/x-portable-graymap' => 'pgm',
'image/x-portable-pixmap' => 'ppm',
'image/x-rgb' => 'rgb',
'image/x-xbitmap' => 'xbm',
'image/x-xpixmap' => 'xpm',
'image/x-xwindowdump' => 'xwd',
'image/png' => 'png',
'image/x-jps' => 'jps',
'image/x-freehand' => 'fh'
][$imageType] ?? '');
}
/*
* Image type 반환
*/
public function getImageType(int $imageType): string
{
return ([
IMAGETYPE_UNKNOWN => 'UNKNOWN',
IMAGETYPE_GIF => 'GIF',
IMAGETYPE_JPEG => 'JPEG',
IMAGETYPE_PNG => 'PNG',
IMAGETYPE_SWF => 'SWF',
IMAGETYPE_PSD => 'PSD',
IMAGETYPE_BMP => 'BMP',
IMAGETYPE_TIFF_II => 'TIFF_II',
IMAGETYPE_TIFF_MM => 'TIFF_MM',
IMAGETYPE_JPC => 'JPC',
IMAGETYPE_JP2 => 'JP2',
IMAGETYPE_JPX => 'JPX',
IMAGETYPE_JB2 => 'JB2',
IMAGETYPE_SWC => 'SWC',
IMAGETYPE_IFF => 'IFF',
IMAGETYPE_WBMP => 'WBMP',
IMAGETYPE_XBM => 'XBM',
IMAGETYPE_ICO => 'ICO',
IMAGETYPE_WEBP => 'WEBP',
IMAGETYPE_AVIF => 'AVIF',
IMAGETYPE_COUNT => 'COUNT'
][$imageType] ?? '');
}
/*
* Image Info 반환
*/
public function getImageInfo(string $path): object
{
$ret = (object)[
'answer' => $this->isImage($path),
'name' => basename($path),
'size' => filesize($path),
'type' => null,
'width' => 0,
'height' => 0,
'ext' => null
];
if(!file_exists($path)) {
return $ret;
}
if($ret->answer) {
[$ret->width, $ret->height, $ret->ext] = getimagesize($path);
$ret->type = $this->getImageType($ret->ext);
}
return $ret;
}
/*
* 파일 삭제
*/
public function remove(string $path): bool
{
if (file_exists($path)) {
return unlink($path);
}
$path = public_path($path);
if (file_exists($path)) {
return unlink($path);
}
return false;
}
/*
* 본문 내용에 없는 이미지를 삭제한다.
*/
public function clear(array $images, string $uploadType, string|null $addPath = null): void
{
$images = array_map(function ($src) {
return last(explode(DIRECTORY_SEPARATOR, $src));
}, $images);
$storagePath = $this->getStoragePath($uploadType, $addPath);
if (is_dir($storagePath)) {
foreach (scandir($storagePath) as $file) {
if (in_array($file, ['.', '..', 'index.php'])) {
continue;
}
if (!in_array($file, $images)) { // 에디터에 없는 이미지면 삭제
$filePath = ($storagePath . $file);
if (is_dir($filePath)) {
continue;
}
unlink($filePath);
}
}
// 이미지가 없으면 디렉토리 삭제
if (count($images) <= 0) {
if(unlink($storagePath . 'index.php')) {
rmdir($storagePath);
}
}
}
unset($images, $uploadType, $addPath, $storagePath);
}
/*
* 이미지 정보 반환
*/
public function getImageFromContent(?string $content): array
{
$dom = new DOMDocument('1.0', 'UTF-8');
libxml_use_internal_errors(true);
$dom->loadHTML('' . $content);
libxml_clear_errors();
$images = [];
foreach($dom->getElementsByTagName('img') as $img) {
$src = canonicalizePath($img->attributes->getNamedItem("src")?->value);
$origin = $img->attributes->getNamedItem("alt")?->value;
$path = public_path($src);
if(!file_exists($path)) {
continue;
}
$imageInfo = $this->getImageInfo($path);
$images[] = (object)[
'origin' => $origin,
'name' => $imageInfo->name,
'path' => $path,
'url' => $src,
'size' => $imageInfo->size,
'type' => $imageInfo->type,
'width' => $imageInfo->width,
'height' => $imageInfo->height
];
}
return $images;
}
}