-
-
Save nicolas-grekas/2665437 to your computer and use it in GitHub Desktop.
| <?php /****************** vi: set fenc=utf-8 ts=4 sw=4 et: ***************** | |
| * | |
| * Copyright : (C) 2012 Nicolas Grekas. All rights reserved. | |
| * Email : [email protected] | |
| * License : http://apache.org/licenses/LICENSE-2.0 | |
| * | |
| * This library is free software; you can redistribute it and/or | |
| * modify it under the terms of the Apache License version 2. | |
| * | |
| ***************************************************************************/ | |
| // This code is obsolete | |
| // Please see https://github.com/nicolas-grekas/Patchwork-UTF8/blob/lab-windows-fs/class/Patchwork/Utf8/WinFsStreamWrapper.php | |
| // for an up to date version, packaged as a unit testes PHP stream wrapper. | |
| namespace Patchwork\PHP\Override; | |
| /** | |
| * Unicode aware filesystem access on MS-Windows. | |
| * | |
| * Based on COM Scripting.FileSystemObject object and 8.3 ShortPaths. | |
| * See also comments on http://www.rooftopsolutions.nl/blog/filesystem-encoding-and-php | |
| * | |
| * Experimental proof of concept only. | |
| */ | |
| class WinfsUtf8 | |
| { | |
| protected static $DIR; | |
| static function hide($file) | |
| { | |
| self::getFs()->GetFile($file)->Attributes |= 2; // Set hidden attribute | |
| } | |
| static function absPath($f) | |
| { | |
| $f = strtr($f, '/', '\\'); | |
| if (isset($f[0])) | |
| { | |
| if ('/' === $f[0] || '\\' === $f[0]) return $f; | |
| if (false !== strpos($f, ':')) return $f; | |
| } | |
| return getcwd() . '\\' . $f; | |
| } | |
| static function ls($dir) | |
| { | |
| try | |
| { | |
| $f = array('.', '..'); | |
| $dir = self::getFs()->getFolder(self::absPath($dir)); | |
| foreach ($dir->SubFolders() as $v) $f[] = $v->Name; | |
| foreach ($dir->Files as $v) $f[] = $v->Name; | |
| } | |
| catch (\Exception $f) | |
| { | |
| $f = array(); | |
| } | |
| unset($dir); | |
| return $f; | |
| } | |
| static function ShortPath($f) | |
| { | |
| $FS = self::getFs(); | |
| $a = self::absPath($f); | |
| try | |
| { | |
| if ($FS->FileExists($a) ) return $FS->GetFile ($a)->ShortPath; | |
| if ($FS->FolderExists($a)) return $FS->GetFolder($a)->ShortPath; | |
| } | |
| catch (com_exception $e) {} | |
| return $f; | |
| } | |
| static function chgrp($f, $group) {return chgrp(self::ShortPath($f), $group);} | |
| static function chmod($f, $mode) {return chmod(self::ShortPath($f), $mode);} | |
| static function chown($f, $user) {return chown(self::ShortPath($f), $user);} | |
| static function copy($from, $to, $context = null) | |
| { | |
| if ($context || !self::getFs()->FileExists(self::absPath($from))) | |
| { | |
| return copy($from, $to, $context); | |
| } | |
| try | |
| { | |
| self::getFs()->CopyFile(self::absPath($from), self::absPath($to), true); | |
| return true; | |
| } | |
| catch (com_exception $e) | |
| { | |
| return false; | |
| } | |
| } | |
| static function file_exists($f) | |
| { | |
| $f = self::absPath($f); | |
| return self::getFs()->FileExists($f) || self::getFs()->FolderExists($f); | |
| } | |
| static function file_get_contents($f, $use_include_path = false, $context = null, $offset = 0, $maxlen = null) | |
| { | |
| if (null === $maxlen) return file_get_contents(self::ShortPath($f), $use_include_path, $context, $offset); | |
| else return file_get_contents(self::ShortPath($f), $use_include_path, $context, $offset, $maxlen); | |
| } | |
| static function file_put_contents($f, $data, $flags = 0, $context = null) | |
| { | |
| try {self::getFs()->CreateTextFile(self::absPath($f), false)->Close();} | |
| catch (com_exception $e) {} | |
| if (null === $context) return file_put_contents(self::ShortPath($f), $data, $flags); | |
| else return file_put_contents(self::ShortPath($f), $data, $flags, $context); | |
| } | |
| static function file($f, $flags = 0, $context = null) | |
| { | |
| if (null === $context) return file(self::ShortPath($f), $flags); | |
| else return file(self::ShortPath($f), $flags, $context); | |
| } | |
| static function fileatime($f) {return fileatime(self::ShortPath($f));} | |
| static function filectime($f) {return filectime(self::ShortPath($f));} | |
| static function filegroup($f) {return filegroup(self::ShortPath($f));} | |
| static function fileinode($f) {return fileinode(self::ShortPath($f));} | |
| static function filemtime($f) {return filemtime(self::ShortPath($f));} | |
| static function fileowner($f) {return fileowner(self::ShortPath($f));} | |
| static function fileperms($f) {return fileperms(self::ShortPath($f));} | |
| static function filesize($f) {return filesize (self::ShortPath($f));} | |
| static function filetype($f) {return filetype (self::ShortPath($f));} | |
| static function fopen($f, $mode, $use_include_path = false, $context = null) | |
| { | |
| switch ($m = substr($mode, 0, 1)) | |
| { | |
| case 'x': $mode[0] = 'w'; | |
| case 'w': | |
| case 'a': | |
| try {self::getFs()->CreateTextFile(self::absPath($f), false)->Close();} | |
| catch (com_exception $e) | |
| { | |
| if ('x' === $m) return false; | |
| } | |
| } | |
| return null === $context | |
| ? fopen(self::ShortPath($f), $mode, $use_include_path) | |
| : fopen(self::ShortPath($f), $mode, $use_include_path, $context); | |
| } | |
| // static function glob($f) {return glob($f);} | |
| static function is_dir($f) {return is_dir (self::ShortPath($f));} | |
| static function is_executable($f) {return is_executable(self::ShortPath($f));} | |
| static function is_file($f) {return is_file (self::ShortPath($f));} | |
| static function is_readable($f) {return is_readable (self::ShortPath($f));} | |
| static function is_writable($f) {return is_writable (self::ShortPath($f));} | |
| static function is_writeable($f) {return is_writeable (self::ShortPath($f));} | |
| static function mkdir($dir, $mode = 0777, $recursive = false, $context = null) | |
| { | |
| return mkdir($dir, $mode, $recursive); | |
| if (null !== $context) return mkdir($dir, $mode, $recursive, $context); | |
| $a = self::absPath($dir); | |
| if ($recursive && 0) | |
| { | |
| $a = explode('\\', $a); | |
| $pre = $a[0]; | |
| array_shift($a); | |
| $b = array(); | |
| foreach ($a as $a) | |
| { | |
| if (!isset($a[0]) || '.' === $a) continue; | |
| if ('..' === $a) $b && array_pop($b); | |
| else $b[]= $a; | |
| } | |
| $a = $pre . implode('\\', $b); | |
| $b = array(); | |
| while (!self::getFs()->FolderExists(dirname($a))) | |
| { | |
| //TODO | |
| } | |
| } | |
| try | |
| { | |
| self::getFs()->CreateFolder($a); | |
| return true; | |
| } | |
| catch (com_exception $e) {} | |
| return mkdir($dir, $mode, $recursive); | |
| } | |
| static function parse_ini_file($f, $process_sections = false) {return parse_ini_file(self::ShortPath($f), $process_sections);} | |
| static function readfile($f, $use_include_path = false, $context = null) | |
| { | |
| return null === $context | |
| ? readfile(self::ShortPath($f), $use_include_path) | |
| : readfile(self::ShortPath($f), $use_include_path, $context); | |
| } | |
| static function realpath($f) {return self::file_exists($f) ? self::getFs()->GetAbsolutePathName(self::absPath($f)) : false;} | |
| static function rename($from, $to, $context = null) | |
| { | |
| if ($context) return rename($from, $to, $context); | |
| $FS = self::getFs(); | |
| $from = self::absPath($from); | |
| $to = self::absPath($to); | |
| if ($FS->FileExists($to)) | |
| { | |
| try | |
| { | |
| $FS->DeleteFile($to, true); | |
| } | |
| catch (com_exception $e) {} | |
| } | |
| else if ($FS->FolderExists($to)) | |
| { | |
| return false; | |
| } | |
| try | |
| { | |
| if ($FS->FileExists($from)) | |
| { | |
| $FS->MoveFile($from, $to); | |
| return true; | |
| } | |
| if ($FS->FolderExists($from)) | |
| { | |
| $FS->MoveFolder($from, $to); | |
| return true; | |
| } | |
| } | |
| catch (com_exception $e) {} | |
| return false; | |
| } | |
| static function rmdir($f, $context = null) | |
| { | |
| return null === $context | |
| ? rmdir(self::ShortPath($f)) | |
| : rmdir(self::ShortPath($f), $context); | |
| } | |
| static function stat($f) {return stat(self::ShortPath($f));} | |
| static function touch($f, $time = null, $atime = null) | |
| { | |
| try {self::getFs()->CreateTextFile(self::absPath($f), false)->Close();} | |
| catch (com_exception $e) {} | |
| return touch(self::ShortPath($f), $time, $atime); | |
| } | |
| static function unlink($f, $context = null) | |
| { | |
| return null === $context | |
| ? unlink(self::ShortPath($f)) | |
| : unlink(self::ShortPath($f), $context); | |
| } | |
| static function dir($f) | |
| { | |
| return self::getFs()->FolderExists(self::absPath($f)) ? new WinfsUtf8Directory($f) : dir($f); | |
| } | |
| static function closedir($d = null) | |
| { | |
| null === $d && $d = self::$DIR; | |
| return $d instanceof WinfsUtf8Directory ? $d->close() : closedir($d); | |
| } | |
| static function opendir($f, $context = null) | |
| { | |
| return self::$DIR = !$context && self::getFs()->FolderExists(self::absPath($f)) ? new WinfsUtf8Directory($f) : opendir($f, $context); | |
| } | |
| static function readdir($d = null) | |
| { | |
| null === $d && $d = self::$DIR; | |
| return $d instanceof WinfsUtf8Directory ? $d->read() : readdir($d); | |
| } | |
| static function rewinddir($d = null) | |
| { | |
| null === $d && $d = self::$DIR; | |
| return $d instanceof WinfsUtf8Directory ? $d->rewind() : rewinddir($d); | |
| } | |
| static function scandir($f, $sorting_order = 0, $context = null) | |
| { | |
| if (null !== $context) return scandir($f, $sorting_order, $context); | |
| $c = self::ls($f); | |
| if (!$c) return scandir($f); | |
| sort($c); | |
| return $c; | |
| } | |
| /* | |
| static function popen($f) {return popen(self::ShortPath($f));} | |
| static function exec($f) {return exec(self::ShortPath($f));} | |
| static function passthru($f) {return passthru(self::ShortPath($f));} | |
| static function proc_open($f) {return proc_open(self::ShortPath($f));} | |
| static function shell_exec($f) {return shell_exec(self::ShortPath($f));} | |
| static function ` `($f) {return ` `(self::ShortPath($f));} | |
| static function system($f) {return system(self::ShortPath($f));} | |
| */ | |
| protected static function getFs() | |
| { | |
| static $FS; | |
| isset($FS) || $FS = new \COM('Scripting.FileSystemObject', null, CP_UTF8); | |
| return $FS; | |
| } | |
| } | |
| class WinfsUtf8Directory extends \Directory | |
| { | |
| public $path, $handle; | |
| protected $childs = array(); | |
| function __construct($path) | |
| { | |
| $this->path = $path; | |
| $this->handle = $this; | |
| $this->childs = WinfsUtf8::ls($path); | |
| if (!$this->childs) | |
| { | |
| $this->childs = scandir($path); | |
| $this->childs || $this->childs = array(); | |
| } | |
| } | |
| function read() | |
| { | |
| return (list(, $c) = each($this->childs)) ? $c : false; | |
| } | |
| function rewind() | |
| { | |
| reset($this->childs); | |
| } | |
| function close() | |
| { | |
| unset($this->path, $this->handle, $this->childs); | |
| } | |
| } |
Hmm... I just happen to learn that on some specific cases the shortpath might include non us-ascii characters, although it's not possible according to MS documentation.
In such a case shorthand functions are not working. (i.e. filemtime). Here's how I implemented filemtime:
static function filemtime($f) {
$a = self::absPath($f);
$file = self::is_dir($f) ? self::getFs()->GetFolder($a) : self::getFs()->GetFile($a);
$dateLastModified = $file->DateLastModified;
return variant_date_to_timestamp($dateLastModified);
}Finally some update,
the code is now unit tested and included in a lab branch of Patchwork/Utf-8:
https://github.com/nicolas-grekas/Patchwork-UTF8/blob/lab-windows-fs/class/Patchwork/Utf8/WinFsStreamWrapper.php
It is now a PHP stream wrapper, a lot easier to use!
Still based on short path because I didn't find a way to "fopen() & co." binary files without it.
@ozanhazer thank you for your hint anyway, I may find a way to remove short paths one day... (or someone else please :) )
Saved the day! Thank you... You may want to add md5_file by the way...