forked from ZwiiCMS-Team/ZwiiCMS
251 lines
6.2 KiB
PHP
251 lines
6.2 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace SleekDB\Classes;
|
||
|
|
||
|
use Closure;
|
||
|
use Exception;
|
||
|
use RecursiveDirectoryIterator;
|
||
|
use RecursiveIteratorIterator;
|
||
|
use SleekDB\Exceptions\IOException;
|
||
|
use SleekDB\Exceptions\JsonException;
|
||
|
|
||
|
/**
|
||
|
* Class IoHelper
|
||
|
* Helper to handle file input/ output.
|
||
|
*/
|
||
|
class IoHelper {
|
||
|
|
||
|
/**
|
||
|
* @param string $path
|
||
|
* @throws IOException
|
||
|
*/
|
||
|
public static function checkWrite(string $path)
|
||
|
{
|
||
|
if(file_exists($path) === false){
|
||
|
$path = dirname($path);
|
||
|
}
|
||
|
// Check if PHP has write permission
|
||
|
if (!is_writable($path)) {
|
||
|
throw new IOException(
|
||
|
"Directory or file is not writable at \"$path\". Please change permission."
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $path
|
||
|
* @throws IOException
|
||
|
*/
|
||
|
public static function checkRead(string $path)
|
||
|
{
|
||
|
// Check if PHP has read permission
|
||
|
if (!is_readable($path)) {
|
||
|
throw new IOException(
|
||
|
"Directory or file is not readable at \"$path\". Please change permission."
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $filePath
|
||
|
* @return string
|
||
|
* @throws IOException
|
||
|
*/
|
||
|
public static function getFileContent(string $filePath): string
|
||
|
{
|
||
|
|
||
|
self::checkRead($filePath);
|
||
|
|
||
|
if(!file_exists($filePath)) {
|
||
|
throw new IOException("File does not exist: $filePath");
|
||
|
}
|
||
|
|
||
|
$content = false;
|
||
|
$fp = fopen($filePath, 'rb');
|
||
|
if(flock($fp, LOCK_SH)){
|
||
|
$content = stream_get_contents($fp);
|
||
|
}
|
||
|
flock($fp, LOCK_UN);
|
||
|
fclose($fp);
|
||
|
|
||
|
if($content === false) {
|
||
|
throw new IOException("Could not retrieve the content of a file. Please check permissions at: $filePath");
|
||
|
}
|
||
|
|
||
|
return $content;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $filePath
|
||
|
* @param string $content
|
||
|
* @throws IOException
|
||
|
*/
|
||
|
public static function writeContentToFile(string $filePath, string $content){
|
||
|
|
||
|
self::checkWrite($filePath);
|
||
|
|
||
|
// Wait until it's unlocked, then write.
|
||
|
if(file_put_contents($filePath, $content, LOCK_EX) === false){
|
||
|
throw new IOException("Could not write content to file. Please check permissions at: $filePath");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $folderPath
|
||
|
* @return bool
|
||
|
* @throws IOException
|
||
|
*/
|
||
|
public static function deleteFolder(string $folderPath): bool
|
||
|
{
|
||
|
self::checkWrite($folderPath);
|
||
|
$it = new RecursiveDirectoryIterator($folderPath, RecursiveDirectoryIterator::SKIP_DOTS);
|
||
|
$files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
|
||
|
foreach ($files as $file) {
|
||
|
self::checkWrite($file);
|
||
|
if ($file->isDir()) {
|
||
|
rmdir($file->getRealPath());
|
||
|
} else {
|
||
|
unlink($file->getRealPath());
|
||
|
}
|
||
|
}
|
||
|
return rmdir($folderPath);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $folderPath
|
||
|
* @throws IOException
|
||
|
*/
|
||
|
public static function createFolder(string $folderPath){
|
||
|
// We don't need to create a folder if it already exists.
|
||
|
if(file_exists($folderPath) === true){
|
||
|
return;
|
||
|
}
|
||
|
self::checkWrite($folderPath);
|
||
|
// Check if the data_directory exists or create one.
|
||
|
if (!file_exists($folderPath) && !mkdir($folderPath, 0777, true) && !is_dir($folderPath)) {
|
||
|
throw new IOException(
|
||
|
'Unable to create the a directory at ' . $folderPath
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $filePath
|
||
|
* @param Closure $updateContentFunction Has to return a string or an array that will be encoded to json.
|
||
|
* @return string
|
||
|
* @throws IOException
|
||
|
* @throws JsonException
|
||
|
*/
|
||
|
public static function updateFileContent(string $filePath, Closure $updateContentFunction): string
|
||
|
{
|
||
|
self::checkRead($filePath);
|
||
|
self::checkWrite($filePath);
|
||
|
|
||
|
$content = false;
|
||
|
|
||
|
$fp = fopen($filePath, 'rb');
|
||
|
if(flock($fp, LOCK_SH)){
|
||
|
$content = stream_get_contents($fp);
|
||
|
}
|
||
|
flock($fp, LOCK_UN);
|
||
|
fclose($fp);
|
||
|
|
||
|
if($content === false){
|
||
|
throw new IOException("Could not get shared lock for file: $filePath");
|
||
|
}
|
||
|
|
||
|
$content = $updateContentFunction($content);
|
||
|
|
||
|
if(!is_string($content)){
|
||
|
$encodedContent = json_encode($content);
|
||
|
if($encodedContent === false){
|
||
|
$content = (!is_object($content) && !is_array($content) && !is_null($content)) ? $content : gettype($content);
|
||
|
throw new JsonException("Could not encode content with json_encode. Content: \"$content\".");
|
||
|
}
|
||
|
$content = $encodedContent;
|
||
|
}
|
||
|
|
||
|
|
||
|
if(file_put_contents($filePath, $content, LOCK_EX) === false){
|
||
|
throw new IOException("Could not write content to file. Please check permissions at: $filePath");
|
||
|
}
|
||
|
|
||
|
|
||
|
return $content;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $filePath
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function deleteFile(string $filePath): bool
|
||
|
{
|
||
|
|
||
|
if(false === file_exists($filePath)){
|
||
|
return true;
|
||
|
}
|
||
|
try{
|
||
|
self::checkWrite($filePath);
|
||
|
}catch(Exception $exception){
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return (@unlink($filePath) && !file_exists($filePath));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param array $filePaths
|
||
|
* @return bool
|
||
|
*/
|
||
|
public static function deleteFiles(array $filePaths): bool
|
||
|
{
|
||
|
foreach ($filePaths as $filePath){
|
||
|
// if a file does not exist, we do not need to delete it.
|
||
|
if(true === file_exists($filePath)){
|
||
|
try{
|
||
|
self::checkWrite($filePath);
|
||
|
if(false === @unlink($filePath) || file_exists($filePath)){
|
||
|
return false;
|
||
|
}
|
||
|
} catch (Exception $exception){
|
||
|
// TODO trigger a warning or exception
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Strip string for secure file access.
|
||
|
* @param string $string
|
||
|
* @return string
|
||
|
*/
|
||
|
public static function secureStringForFileAccess(string $string): string
|
||
|
{
|
||
|
return (str_replace(array(".", "/", "\\"), "", $string));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Appends a slash ("/") to the given directory path if there is none.
|
||
|
* @param string $directory
|
||
|
*/
|
||
|
public static function normalizeDirectory(string &$directory){
|
||
|
if(!empty($directory) && substr($directory, -1) !== "/") {
|
||
|
$directory .= "/";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the amount of files in folder.
|
||
|
* @param string $folder
|
||
|
* @return int
|
||
|
* @throws IOException
|
||
|
*/
|
||
|
public static function countFolderContent(string $folder): int
|
||
|
{
|
||
|
self::checkRead($folder);
|
||
|
$fi = new \FilesystemIterator($folder, \FilesystemIterator::SKIP_DOTS);
|
||
|
return iterator_count($fi);
|
||
|
}
|
||
|
}
|