First commit

This commit is contained in:
Pierre Hubert
2016-11-19 12:08:12 +01:00
commit 990540b2b9
4706 changed files with 931207 additions and 0 deletions

View File

@@ -0,0 +1,440 @@
<?php
namespace RocketTheme\Toolbox\File;
/**
* Implements Universal File Reader.
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
class File implements FileInterface
{
/**
* @var string
*/
protected $filename;
/**
* @var resource
*/
protected $handle;
/**
* @var bool|null
*/
protected $locked;
/**
* @var string
*/
protected $extension;
/**
* @var string Raw file contents.
*/
protected $raw;
/**
* @var array Parsed file contents.
*/
protected $content;
/**
* @var array
*/
protected $settings = [];
/**
* @var array|File[]
*/
static protected $instances = array();
/**
* Get file instance.
*
* @param string $filename
* @return FileInterface
*/
public static function instance($filename)
{
if (!is_string($filename) && $filename) {
throw new \InvalidArgumentException('Filename should be non-empty string');
}
if (!isset(static::$instances[$filename])) {
static::$instances[$filename] = new static;
static::$instances[$filename]->init($filename);
}
return static::$instances[$filename];
}
/**
* Set/get settings.
*
* @param array $settings
* @return array
*/
public function settings(array $settings = null)
{
if ($settings !== null) {
$this->settings = $settings;
}
return $this->settings;
}
/**
* Get setting.
*
* @param string $setting
* @param mixed $default
* @return mixed
*/
public function setting($setting, $default = null)
{
return isset($this->settings[$setting]) ? $this->settings[$setting] : $default;
}
/**
* Prevent constructor from being used.
*
* @internal
*/
protected function __construct()
{
}
/**
* Prevent cloning.
*
* @internal
*/
protected function __clone()
{
//Me not like clones! Me smash clones!
}
/**
* Set filename.
*
* @param $filename
*/
protected function init($filename)
{
$this->filename = $filename;
}
/**
* Free the file instance.
*/
public function free()
{
if ($this->locked) {
$this->unlock();
}
$this->content = null;
$this->raw = null;
unset(static::$instances[$this->filename]);
}
/**
* Get/set the file location.
*
* @param string $var
* @return string
*/
public function filename($var = null)
{
if ($var !== null) {
$this->filename = $var;
}
return $this->filename;
}
/**
* Return basename of the file.
*
* @return string
*/
public function basename()
{
return basename($this->filename, $this->extension);
}
/**
* Check if file exits.
*
* @return bool
*/
public function exists()
{
return is_file($this->filename);
}
/**
* Return file modification time.
*
* @return int|bool Timestamp or false if file doesn't exist.
*/
public function modified()
{
return is_file($this->filename) ? filemtime($this->filename) : false;
}
/**
* Lock file for writing. You need to manually unlock().
*
* @param bool $block For non-blocking lock, set the parameter to false.
* @return bool
* @throws \RuntimeException
*/
public function lock($block = true)
{
if (!$this->handle) {
if (!$this->mkdir(dirname($this->filename))) {
throw new \RuntimeException('Creating directory failed for ' . $this->filename);
}
$this->handle = @fopen($this->filename, 'cb+');
if (!$this->handle) {
$error = error_get_last();
throw new \RuntimeException("Opening file for writing failed on error {$error['message']}");
}
}
$lock = $block ? LOCK_EX : LOCK_EX | LOCK_NB;
return $this->locked = $this->handle ? flock($this->handle, $lock) : false;
}
/**
* Returns true if file has been locked for writing.
*
* @return bool|null True = locked, false = failed, null = not locked.
*/
public function locked()
{
return $this->locked;
}
/**
* Unlock file.
*
* @return bool
*/
public function unlock()
{
if (!$this->handle) {
return;
}
if ($this->locked) {
flock($this->handle, LOCK_UN);
$this->locked = null;
}
fclose($this->handle);
$this->handle = null;
}
/**
* Check if file can be written.
*
* @return bool
*/
public function writable()
{
return is_writable($this->filename) || $this->writableDir(dirname($this->filename));
}
/**
* (Re)Load a file and return RAW file contents.
*
* @return string
*/
public function load()
{
$this->raw = $this->exists() ? (string) file_get_contents($this->filename) : '';
$this->content = null;
return $this->raw;
}
/**
* Get/set raw file contents.
*
* @param string $var
* @return string
*/
public function raw($var = null)
{
if ($var !== null) {
$this->raw = (string) $var;
$this->content = null;
}
if (!is_string($this->raw)) {
$this->raw = $this->load();
}
return $this->raw;
}
/**
* Get/set parsed file contents.
*
* @param mixed $var
* @return string|array
*/
public function content($var = null)
{
if ($var !== null) {
$this->content = $this->check($var);
// Update RAW, too.
$this->raw = $this->encode($this->content);
} elseif ($this->content === null) {
// Decode RAW file.
$this->content = $this->decode($this->raw());
}
return $this->content;
}
/**
* Save file.
*
* @param mixed $data Optional data to be saved, usually array.
* @throws \RuntimeException
*/
public function save($data = null)
{
if ($data !== null) {
$this->content($data);
}
if (!$this->locked) {
// Obtain blocking lock or fail.
if (!$this->lock()) {
throw new \RuntimeException('Obtaining write lock failed on file: ' . $this->filename);
}
$lock = true;
}
// As we are using non-truncating locking, make sure that the file is empty before writing.
if (@ftruncate($this->handle, 0) === false || @fwrite($this->handle, $this->raw()) === false) {
$this->unlock();
throw new \RuntimeException('Saving file failed: ' . $this->filename);
}
if (isset($lock)) {
$this->unlock();
}
// Touch the directory as well, thus marking it modified.
@touch(dirname($this->filename));
}
/**
* Rename file in the filesystem if it exists.
*
* @param $filename
* @return bool
*/
public function rename($filename)
{
if ($this->exists() && !@rename($this->filename, $filename)) {
return false;
}
unset(static::$instances[$this->filename]);
static::$instances[$filename] = $this;
$this->filename = $filename;
return true;
}
/**
* Delete file from filesystem.
*
* @return bool
*/
public function delete()
{
return unlink($this->filename);
}
/**
* Check contents and make sure it is in correct format.
*
* Override in derived class.
*
* @param string $var
* @return string
*/
protected function check($var)
{
return (string) $var;
}
/**
* Encode contents into RAW string.
*
* Override in derived class.
*
* @param string $var
* @return string
*/
protected function encode($var)
{
return (string) $var;
}
/**
* Decode RAW string into contents.
*
* Override in derived class.
*
* @param string $var
* @return string mixed
*/
protected function decode($var)
{
return (string) $var;
}
/**
* @param string $dir
* @return bool
* @throws \RuntimeException
* @internal
*/
protected function mkdir($dir)
{
// Silence error for open_basedir; should fail in mkdir instead.
if (!@is_dir($dir)) {
$success = @mkdir($dir, 0777, true);
if (!$success) {
$error = error_get_last();
throw new \RuntimeException("Creating directory '{$dir}' failed on error {$error['message']}");
}
}
return true;
}
/**
* @param string $dir
* @return bool
* @internal
*/
protected function writableDir($dir)
{
if ($dir && !file_exists($dir)) {
return $this->writableDir(dirname($dir));
}
return $dir && is_dir($dir) && is_writable($dir);
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace RocketTheme\Toolbox\File;
/**
* Defines FileInterface.
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
interface FileInterface
{
/**
* Get file instance.
*
* @param string $filename
* @return mixed
*/
public static function instance($filename);
/**
* Free the file instance.
*/
public function free();
/**
* Check if file exits.
*
* @return bool
*/
public function exists();
/**
* Return file modification time.
*
* @return int Timestamp
*/
public function modified();
/**
* Lock file for writing. Lock gets automatically released during the save().
*
* @param bool $block For non-blocking lock, set the parameter to false.
* @return bool
*/
public function lock($block = true);
/**
* Returns true if file has been locked for writing.
*
* @return bool|null True = locked, false = failed, null = not locked.
*/
public function locked();
/**
* Unlock file.
*
* @return bool
*/
public function unlock();
/**
* Check if file can be written.
*
* @return bool
*/
public function writable();
/**
* (Re)Load a file and return its contents.
*
* @return string
*/
public function load();
/**
* Get/set raw file contents.
*
* @param string $var
* @return string
*/
public function raw($var = null);
/**
* Get/set parsed file contents.
*
* @param string $var
* @return string
*/
public function content($var = null);
/**
* Save file.
*
* @param string $data Optional data to be saved.
* @throws \RuntimeException
*/
public function save($data = null);
/**
* Delete file from filesystem.
*
* @return bool
*/
public function delete();
}

View File

@@ -0,0 +1,76 @@
<?php
namespace RocketTheme\Toolbox\File;
/**
* Implements INI File reader.
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
class IniFile extends File
{
/**
* @var string
*/
protected $extension = '.ini';
/**
* @var array|File[]
*/
static protected $instances = array();
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
* @throws \RuntimeException
*/
protected function check($var)
{
if (!is_array($var)) {
throw new \RuntimeException('Provided data is not an array');
}
return $var;
}
/**
* Encode configuration object into RAW string (INI).
*
* @param array $var
* @return string
* @throws \RuntimeException
*/
protected function encode($var)
{
$string = '';
foreach ($var as $key => $value) {
$string .= $key . '="' . preg_replace(
['/"/', '/\\\/', "/\t/", "/\n/", "/\r/"],
['\"', '\\\\', '\t', '\n', '\r'],
$value
) . "\"\n";
}
return $string;
}
/**
* Decode INI file into contents.
*
* @param string $var
* @return array
* @throws \RuntimeException
*/
protected function decode($var)
{
$var = @parse_ini_file($this->filename);
if ($var === false) {
throw new \RuntimeException("Decoding file '{$this->filename}' failed'");
}
return $var;
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace RocketTheme\Toolbox\File;
/**
* Implements Json File reader.
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
class JsonFile extends File
{
/**
* @var string
*/
protected $extension = '.json';
/**
* @var array|File[]
*/
static protected $instances = array();
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
*/
protected function check($var)
{
return (array) $var;
}
/**
* Encode contents into RAW string.
*
* @param string $var
* @params bitmask $options
* @return string
*/
protected function encode($var, $options = 0)
{
return (string) json_encode($var, $options);
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @param bool $assoc
* @return array mixed
*/
protected function decode($var, $assoc = false)
{
return (array) json_decode($var, $assoc);
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace RocketTheme\Toolbox\File;
/**
* Implements Log File reader.
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
class LogFile extends File
{
/**
* @var array|File[]
*/
static protected $instances = array();
/**
* Constructor.
*/
protected function __construct()
{
parent::__construct();
$this->extension = '.log';
}
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
*/
protected function check($var)
{
return (array) $var;
}
/**
* Encode contents into RAW string (unsupported).
*
* @param string $var
* @return string|void
* @throws \Exception
*/
protected function encode($var)
{
throw new \Exception('Saving log file is forbidden.');
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @return array mixed
*/
protected function decode($var)
{
$lines = (array) preg_split('#(\r\n|\n|\r)#', $var);
$results = array();
foreach ($lines as $line) {
preg_match('#^\[(.*)\] (.*) @ (.*) @@ (.*)$#', $line, $matches);
if ($matches) {
$results[] = ['date' => $matches[1], 'message' => $matches[2], 'url' => $matches[3], 'file' => $matches[4]];
}
}
return $results;
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace RocketTheme\Toolbox\File;
use \Symfony\Component\Yaml\Yaml as YamlParser;
/**
* Implements Markdown File reader.
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
class MarkdownFile extends File
{
/**
* @var string
*/
protected $extension = '.md';
/**
* @var array|File[]
*/
static protected $instances = array();
/**
* Get/set file header.
*
* @param array $var
*
* @return array
*/
public function header(array $var = null)
{
$content = $this->content();
if ($var !== null) {
$content['header'] = $var;
$this->content($content);
}
return $content['header'];
}
/**
* Get/set markdown content.
*
* @param string $var
*
* @return string
*/
public function markdown($var = null)
{
$content = $this->content();
if ($var !== null) {
$content['markdown'] = (string) $var;
$this->content($content);
}
return $content['markdown'];
}
/**
* Get/set frontmatter content.
*
* @param string $var
*
* @return string
*/
public function frontmatter($var = null)
{
$content = $this->content();
if ($var !== null) {
$content['frontmatter'] = (string) $var;
$this->content($content);
}
return $content['frontmatter'];
}
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
*/
protected function check($var)
{
$var = (array) $var;
if (!isset($var['header']) || !is_array($var['header'])) {
$var['header'] = array();
}
if (!isset($var['markdown']) || !is_string($var['markdown'])) {
$var['markdown'] = '';
}
return $var;
}
/**
* Encode contents into RAW string.
*
* @param string $var
* @return string
*/
protected function encode($var)
{
// Create Markdown file with YAML header.
$o = (!empty($var['header']) ? "---\n" . trim(YamlParser::dump($var['header'], 5)) . "\n---\n\n" : '') . $var['markdown'];
// Normalize line endings to Unix style.
$o = preg_replace("/(\r\n|\r)/", "\n", $o);
return $o;
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @return array mixed
*/
protected function decode($var)
{
$content = [
'header' => false,
'frontmatter' => ''
];
$frontmatter_regex = "/^---\n(.+?)\n---\n{0,}(.*)$/uis";
// Normalize line endings to Unix style.
$var = preg_replace("/(\r\n|\r)/", "\n", $var);
// Parse header.
preg_match($frontmatter_regex, ltrim($var), $m);
if(!empty($m)) {
$content['frontmatter'] = preg_replace("/\n\t/", "\n ", $m[1]);
// Try native PECL YAML PHP extension first if available.
if ($this->setting('native') && function_exists('yaml_parse')) {
$data = $content['frontmatter'];
if ($this->setting('compat', true)) {
// Fix illegal @ start character.
$data = preg_replace('/ (@[\w\.\-]*)/', " '\${1}'", $data);
}
// Safely decode YAML.
$saved = @ini_get('yaml.decode_php');
@ini_set('yaml.decode_php', 0);
$content['header'] = @yaml_parse("---\n" . $data . "\n...");
@ini_set('yaml.decode_php', $saved);
}
if ($content['header'] === false) {
// YAML hasn't been parsed yet (error or extension isn't available). Fall back to Symfony parser.
$content['header'] = (array) YamlParser::parse($content['frontmatter']);
}
$content['markdown'] = $m[2];
} else {
$content['header'] = [];
$content['markdown'] = $var;
}
return $content;
}
}

View File

@@ -0,0 +1,160 @@
<?php
namespace RocketTheme\Toolbox\File;
/**
* Implements Gettext Mo File reader (readonly).
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
class MoFile extends File
{
/**
* @var string
*/
protected $extension = '.mo';
protected $pos = 0;
protected $str;
protected $len;
protected $endian;
/**
* @var array|File[]
*/
static protected $instances = array();
/**
* File can never be written.
*
* @return bool
*/
public function writable()
{
return false;
}
/**
* Prevent saving file.
*
* @throws \BadMethodCallException
*/
public function save($data = null)
{
throw new \BadMethodCallException('save() not supported for .mo files.');
}
/**
* Prevent deleting file from filesystem.
*
* @return bool
*/
public function delete()
{
return false;
}
/**
* @param $var
* @return array
* @throws \RuntimeException
*/
public function decode($var)
{
$this->endian = 'V';
$this->str = $var;
$this->len = strlen($var);
$magic = $this->readInt() & 0xffffffff;
if ($magic === 0x950412de) {
// Low endian.
$this->endian = 'V';
} elseif ($magic === 0xde120495) {
// Big endian.
$this->endian = 'N';
} else {
throw new \RuntimeException('Not a Gettext file (.mo).');
}
// Skip revision number.
$rev = $this->readInt();
// Total count.
$total = $this->readInt();
// Offset of original table.
$originals = $this->readInt();
// Offset of translation table.
$translations = $this->readInt();
// Each table consists of string length and offset of the string.
$this->seek($originals);
$table_originals = $this->readIntArray($total * 2);
$this->seek($translations);
$table_translations = $this->readIntArray($total * 2);
$items = [];
for ($i = 0; $i < $total; $i++) {
$this->seek($table_originals[$i * 2 + 2]);
// TODO: Original string can have context concatenated on it. We do not yet support that.
$original = $this->read($table_originals[$i * 2 + 1]);
if ($original) {
$this->seek($table_translations[$i * 2 + 2]);
// TODO: Plural forms are stored by letting the plural of the original string follow the singular of the original string, separated through a NUL byte.
$translated = $this->read($table_translations[$i * 2 + 1]);
$items[$original] = $translated;
}
}
return $items;
}
/**
* @return int
*/
protected function readInt()
{
$read = $this->read(4);
if ($read === false) {
return false;
}
$read = unpack($this->endian, $read);
return array_shift($read);
}
/**
* @param $count
* @return array
*/
protected function readIntArray($count)
{
return unpack($this->endian . $count, $this->read(4 * $count));
}
/**
* @param $bytes
* @return string
*/
private function read($bytes)
{
$data = substr($this->str, $this->pos, $bytes);
$this->seek($this->pos + $bytes);
return $data;
}
/**
* @param $pos
* @return mixed
*/
private function seek($pos)
{
$this->pos = $pos < $this->len ? $pos : $this->len;
return $this->pos;
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace RocketTheme\Toolbox\File;
/**
* Implements PHP File reader.
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
class PhpFile extends File
{
/**
* @var string
*/
protected $extension = '.php';
/**
* @var array|File[]
*/
static protected $instances = array();
/**
* Saves PHP file and invalidates opcache.
*
* @param mixed $data Optional data to be saved, usually array.
* @throws \RuntimeException
*/
public function save($data = null)
{
parent::save($data);
// Invalidate configuration file from the opcache.
if (function_exists('opcache_invalidate')) {
// PHP 5.5.5+
@opcache_invalidate($this->filename, true);
} elseif (function_exists('apc_invalidate')) {
// APC
@apc_invalidate($this->filename);
}
}
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
* @throws \RuntimeException
*/
protected function check($var)
{
if (!(is_array($var) || is_object($var))) {
throw new \RuntimeException('Provided data is not an array');
}
return $var;
}
/**
* Encode configuration object into RAW string (PHP class).
*
* @param array $var
* @return string
* @throws \RuntimeException
*/
protected function encode($var)
{
// Build the object variables string
return "<?php\nreturn {$this->encodeArray((array) $var)};\n";
}
/**
* Method to get an array as an exported string.
*
* @param array $a The array to get as a string.
* @param int $level Used internally to indent rows.
*
* @return array
*/
protected function encodeArray(array $a, $level = 0)
{
$r = [];
foreach ($a as $k => $v) {
if (is_array($v) || is_object($v)) {
$r[] = var_export($k, true) . " => " . $this->encodeArray((array) $v, $level + 1);
} else {
$r[] = var_export($k, true) . " => " . var_export($v, true);
}
}
$space = str_repeat(" ", $level);
return "[\n {$space}" . implode(",\n {$space}", $r) . "\n{$space}]";
}
/**
* Decode PHP file into contents.
*
* @param string $var
* @return array
*/
protected function decode($var)
{
$var = (array) include $this->filename;
return $var;
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace RocketTheme\Toolbox\File;
use Symfony\Component\Yaml\Exception\DumpException;
use Symfony\Component\Yaml\Exception\ParseException;
use \Symfony\Component\Yaml\Yaml as YamlParser;
/**
* Implements YAML File reader.
*
* @package RocketTheme\Toolbox\File
* @author RocketTheme
* @license MIT
*/
class YamlFile extends File
{
/**
* @var array|File[]
*/
static protected $instances = array();
/**
* Constructor.
*/
protected function __construct()
{
parent::__construct();
$this->extension = '.yaml';
}
/**
* Check contents and make sure it is in correct format.
*
* @param array $var
* @return array
*/
protected function check($var)
{
return (array) $var;
}
/**
* Encode contents into RAW string.
*
* @param string $var
* @return string
* @throws DumpException
*/
protected function encode($var)
{
return (string) YamlParser::dump($var, $this->setting('inline', 5), $this->setting('indent', 2), true, false);
}
/**
* Decode RAW string into contents.
*
* @param string $var
* @return array mixed
* @throws ParseException
*/
protected function decode($var)
{
$data = false;
// Try native PECL YAML PHP extension first if available.
if ($this->setting('native') && function_exists('yaml_parse')) {
if ($this->setting('compat', true)) {
// Fix illegal @ start character.
$data = preg_replace('/ (@[\w\.\-]*)/', " '\${1}'", $var);
} else {
$data = $var;
}
// Safely decode YAML.
$saved = @ini_get('yaml.decode_php');
@ini_set('yaml.decode_php', 0);
$data = @yaml_parse($data);
@ini_set('yaml.decode_php', $saved);
}
return $data !== false ? $data : (array) YamlParser::parse($var);
}
}