Get filesystem path from a PHP streamwrapper? - php

If I have a file in a php streamwrapper, such as "media://icon.png" how can I get it to tell me the filesystem path of that file, assuming it's on a filesystem?
I looked on the documentation page but didn't see a method to return the streamwrapper path.

The StreamWrapper class represents generic streams. Because not all streams are backed by the notion of a filesystem, there isn't a generic method for this.
If the uri property from stream_get_meta_data isn't working for your implementation, you can record the information during open time then access it via stream_get_meta_data. Example:
class MediaStream {
public $localpath = null;
public function stream_open($path, $mode, $options, &$opened_path)
{
$this->localpath = realpath(preg_replace('+^media://+', '/', $path));
return fopen($this->localpath, $mode, $options);
}
}
stream_wrapper_register('media', 'MediaStream');
$fp = fopen('media://tmp/icon.png', 'r');
$data = stream_get_meta_data($fp);
var_dump(
$data['wrapper_data']->localpath
);
Of course, there's always a brute force approach: after creating your resource, you can call fstat, which includes the device and inode. You can then open that device, walk its directory structure, and find that inode. See here for an example.

Related

In Laravel 5.6, is it necessary to check if a directory exists before creation? (Situational)

In a function used in Image Storage, I realized I MAY have been over-complicating the code. I have two examples that both return the same results.
First Example:
public function image(Request $request, User $user)
{
$file = $request->file('image');
$path = "user_" . $user->id . "_images";
$filename = $path . "/test.jpg";
Storage::disk('public')->put($filename, File::get($file));
return back();
}
This example was tested in two scenarios. Once with NO directory inside public, and it returned by creating public/user_1_images/test.jpg ...
The second scenario, we already had public/user_1_images/ as an empty directory. Meaning it already exists. So instead, it just put the file inside of there without creating the folder.
Second Example:
public function image(Request $request, User $user)
{
$file = $request->file('image');
$path = "user_" . $user->id . "_images";
if (!Storage::disk('public')->exists($path))
{
Storage::makeDirectory($path, 0755, true, true);
}
$filename = $path . "/test.jpg";
Storage::disk('public')->put($filename, File::get($file));
return back();
}
In this example, we're checking to see if public/user_1_images/ exists, and if it does not, we're creating the folder, and then putting the image inside.
Would I need to do this, if I can just have it automatically check/create/not-create by titling the file "user_1_images/test.jpg"?
It all depends on the driver you use. All drivers must implement AdapterInterface interface and that's the only requirement they must meet.
I had a brief look at some of the popular drivers:
Local driver always calls ensureDirectory() before writing to a path, hence there is no need to create directory manually.
FTP driver always calls ensureDirectory() before writing to a path, hence there is no need to create directory manually.
S3 driver doesn't ensure a directory exist, but all necessary directories are created in S3 when it's asked to write to a path that contains directories. So again, no need to create directory manually.
So it seems that with most (if not all) drivers you can simply write to a path and not worry whether directory exists or not.

Stream from a temporary file, then delete when finished?

I'm writing a temporary file by running a couple of external Unix tools over a PDF file (basically I'm using QPDF and sed to alter the colour values. Don't ask.):
// Uncompress PDF using QPDF (doesn't read from stdin, so needs tempfile.)
$compressed_file_path = tempnam(sys_get_temp_dir(), 'cruciverbal');
file_put_contents($compressed_file_path, $response->getBody());
$uncompressed_file_path = tempnam(sys_get_temp_dir(), 'cruciverbal');
$command = "qpdf --qdf --object-streams=disable '$compressed_file_path' '$uncompressed_file_path'";
exec($command, $output, $return_value);
// Run through sed (could do this bit with streaming stdin/stdout)
$fixed_file_path = tempnam(sys_get_temp_dir(), 'cruciverbal');
$command = "sed s/0.298039215/0.0/g < '$uncompressed_file_path' > '$fixed_file_path'";
exec($command, $output, $return_value);
So, when this is done I'm left with a temporary file on disk at $fixed_file_path. (NB: While I could do the whole sed process streamed in-memory without a tempfile, the QPDF utility requires an actual file as input, for good reasons.)
In my existing process, I then read the whole $fixed_file_path file in as a string, delete it, and hand the string off to another class to go do things with.
I'd now like to change that last part to using a PSR-7 stream, i.e. a \Guzzle\Psr7\Stream object. I figure it'll be more memory-efficient (I might have a few of these in the air at once) and it'll need to be a stream in the end.
However, I'm not sure then how I'd delete the temporary file when the (third-party) class I'd handed the stream off to is finished with it. Is there a method of saying "...and delete that when you're finished with it"? Or auto-cleaning my temporary files in some other way, without keeping track of them manually?
I'd been vaguely considering rolling my own SelfDestructingFileStream, but that seemed like overkill and I thought I might be missing something.
Sounds like what you want is something like this:
<?php
class TempFile implements \Psr\Http\Message\StreamInterface {
private $resource;
public function __construct() {
$this->resource = tmpfile();
}
public function __destruct() {
$this->close();
}
public function getFilename() {
return $this->getMetadata('uri');
}
public function getMetadata($key = null) {
$data = stream_get_meta_data($this->resource);
if($key) {
return $data[$key];
}
return $data;
}
public function close() {
fclose($this->resource);
}
// TODO: implement methods from https://github.com/php-fig/http-message/blob/master/src/StreamInterface.php
}
Have QPDF write to $tmpFile->getFilename() and then you can pass the whole object off to your Guzzle/POST since it's PSR-7 compliant and then the file will delete itself when it goes out of scope.

PHPUnit - testing function which uses realpath()

I have a function that checks if user supplied filename is under the /opt/ directory.
It uses realpath so users can't pass in something like '../etc/passwd' for security reasons.
function check_file_path($relative_filename) {
$abspath = realpath('/opt/' . $relative_filename);
return ($abspath && strpos($abspath , '/opt/') === 0);
}
Now realpath will return false if the file doesn't exist on the fs.
When PHPUnit is running (on dev machine, not on the server), the path does not exist thus we cannot do a positive test.
I've looked into vfsstream but it doesn't play nice with realpath()
org\bovigo\vfs\vfsStream::setup('home');
$file = org\bovigo\vfs\vfsStream::url('home/test.txt');
file_put_contents($file, "The new contents of the file");
$abspath = realpath('vfs://home/test.txt');
// $abspath will be false
Any suggestion to mock a filesystem so that realpath will work?
You can use runkit. It's extension of php.
But if your function calling inside any namespace you can define your own function in that namespace.

Translating paths in PHP from (*nix) server to (winxp) dev machine

I'm working on a PHP project that has a lot of hard coded paths in it. I'm not the main developer, just working on a small part of the project.
I'd like to be able to test my changes locally before committing them, but my directory structure is completely different. For example, there's a lot of this in the code:
require_once("/home/clientx/htdocs/include.php")
Which doesn't work on my local WAMP server because the path is different. Is there a way to tell either WAMP or XP that "/home/clientx/htdocs/" really means "c:/shared/clients/clientx"?
If its a local copy, do a search and replace on the whole directory , Please don't forget trailing slash. And when you commit the code, do reverse.
This is the solution, if you don't want to add extra variables and stuff (because that would change other developers' code/work/dependencies (if any)
search "/home/clientx/htdocs/" and replace to this: "c:/shared/clients/clientx/"
Always use $_SERVER['DOCUMENT_ROOT'] instead of hardcoded path.
require_once($_SERVER['DOCUMENT_ROOT']."/include.php")
as for your wamb environment, you will need a dedicated drive to simulate file structure. You can use NTFS tools or simple subst command to map some directory to a drive.
Create /home/clientx/htdocs/ folder on this drive and change your httpd.conf to reflect it.
But again, you will do yourself a huge favor by convincing your coworkers to stop using hardcoded paths
WARNING: ONLY USE THIS SOLUTION FOR EMERGENCY REPAIRS, NEVER FOR LONGER PRODUCTION CODE
Define a class with rewriting methods, see http://php.net/manual/en/class.streamwrapper.php
<?php
class YourEmergencyWrapper {
static $from = '/home/clientx/htdocs/';
static $to = 'c:/shared/clients/client';
private $resource = null;
//...some example stream_* functions, be sure to implement them all
function stream_open($path,$mode,$options=null,&$opened_path){
$path = self::rewrite($path);
self::restore();
$this->resource = fopen($path,$mode,$options);
self::reenable();
$opened_path = $path;
return is_resource($this->resource);
}
function stream_read($count){
self::restore();
$ret = fread($this->resource,$count);
self::reenable();
return $ret;
}
function stream_eof(){
self::restore();
$ret = feof($this->resource);
self::reenable();
return $ret;
}
function stream_stat(){
self::restore();
$ret = fstat($this->resource);
self::reenable();
return $ret;
}
static function rewrite($path){
if(strpos($path,self::$from)===0) $path = self::$to.substr($path,strlen(self::$from));
return $path;
}
//... other functions
private static function restore(){
stream_wrapper_restore('file');
}
private static function reenable(){
stream_wrapper_unregister('file');
stream_wrapper_register('file',__CLASS__);
}
}
stream_wrapper_unregister('file');
stream_wrapper_register('file','YourEmergencyWrapper');
Seriously, only some local debugging on your own dev-server. You can force it as an auto_prepend on almost any code. Left some function yet be implemented ;P

How do I obtain the path to an image when writing a MediaWiki extension?

I'm trying to write a simple extension for MediaWiki but I can't anywhere find in simple terms, how to call functions within the application to give me the 'hashed' path to an uploaded file.
I've tried the following function call which some searching indicated would return the path but it results in an undefined function error.
//$input is set to 'Image:Test.png' or similar
function noxmagicSVG($input, $args) {
global $wgUploadPath;
$imagePathi = $wgUploadDirectory . wfGetHashPath($input, false) . $input;
return $imagePathi;
}
The following worked for me in a pre-release of MediaWiki 1.16:
$url = Image::imageUrl( 'Ballarddesk.png' );

Categories