Getting parts of a URL in PHP - php

How can I extract the following parts using PHP function:
The Domain
The path without the file
The file
The file with Extension
The file without Extension
The scheme
The port
The query
The fragment
(add any other that you think would be useful)
Ex.1
https://stackoverflow.com/users/test/login.php?q=san&u=post#top
The Domain (stackoverflow.com)
The path without the file (/users/test/)
The file(login.php)
The file Extension (.php)
The file without Extension (login)
The scheme(https:)
The port(return empty string)
The query(q=san&u=post)
The fragment(top)
Ex: 2 stackoverflow.com/users/test/login.php?q=san&u=post#top
The Domain (stackoverflow.com)
The path without the file (/users/test/)
The file(login.php)
The file Extension (.php)
The file without Extension (login)
The scheme(return empty string)
The port(return empty string)
The query(q=san&u=post)
The fragment(top)
Ex: 3 /users/test/login.php?q=san&u=post#top
The path without the file (/users/test/)
The file(login.php)
The file Extension (.php)
The file without Extension (login)
The query(q=san&u=post)
The fragment(top)
For remaining (return empty string)
Ex: 4 /users/test/login?q=san&u=post#top
The path without the file (/users/test/)
The file(login)
The file Extension (return empty string)
The file without Extension (login)
The query(q=san&u=post)
The fragment(top)
For remaining (return empty string)
Ex: 5 login?q=san&u=post#top
The file(login)
The file Extension (return empty string)
The file without Extension (login)
The query(q=san&u=post)
The fragment(top)
For remaining (return empty string)
Ex: 6 ?q=san&u=post
The query(q=san&u=post)
For remaining (return empty string)
I checked parse_url function, but doesn't return what I need. Since, I'm beginner in PHP, it was difficult for me. If you have any idea, please answer.
Thanks in advance.

PHP provides a parse_url function.
This function parses a URL and returns an associative array containing
any of the various components of the URL that are present.
This function is not meant to validate the given URL, it only breaks
it up into the above listed parts. Partial URLs are also accepted,
parse_url() tries its best to parse them correctly.
You can see the test cases executed here.
$urls = array(
"https://stackoverflow.com/users/test/login.php?q=san&u=post#top",
"/users/test/login.php?q=san&u=post#top",
"?q=san&u=post#top",
"login.php?q=san&u=post#top",
"/users/test/login?q=san&u=post#top",
"login?q=san&u=post#top"
);
foreach( $urls as $x ) {
echo $x . "\n";
var_dump( parse_url($x) );
}

I"m using this to locate the root and webroot
<?php
/**
* #brief get paths from the location where it was executed.
*/
class PathHelper {
/**
* #brief This function tries to determine the FileSystem root of the application. (needs to be executed in the root)
* #return string
*/
public static function locateRoot($file) {
$dir = dirname($file);
/** FIX Required for WINDOWS * */
$dir = preg_replace('/\\\\/', '/', $dir);
$dir = preg_replace('/\\\/', '/', $dir);
return $dir;
}
/**
* #brief This function tries to determine the WebRoot. (needs to be executed in the root)
* #return string
*/
public static function locateWebroot($file) {
$docroot = $_SERVER['DOCUMENT_ROOT'];
$dir = dirname($file);
if ($dir == $docroot) {
$webroot = "";
} else {
$webroot = substr_replace($dir, '', 0, strlen($docroot));
}
/** FIX Required for WINDOWS * */
$webroot = preg_replace('/\\\\/', '/', $webroot);
$webroot = preg_replace('/\\\/', '/', $webroot);
return $webroot;
}
}
I set this as a constant so i can use it throughout my application.
For example:
For a menu you can do something like this:
// the requested url
$requestedUrl = $_SERVER['REQUEST_URI'];
// remove the webroot from the requested url
$requestedUrl = str_replace(WEBROOT, "", $_SERVER['REQUEST_URI']);
// take away the / if they still exist at the beginning
$requestedUrl = ltrim($requestedUrl, "/");
Then i got this:
index.php?controller=User&action=overview
This equals to my url of one of my menu items.
You could use explode on this last url to find all the other values you want.
Edit: Its probably better to use parse_url(). I am not used to all the functions in PHP but if nothing works then this is atleast a fallback.

Related

URL is being changed unexpectedly when using custom redirect with constants

My site uses a PLESK server. I have recently changed the document root from httpdocs to httpdocs/public. This is to add additional security to privately used files.
However, I have found that it isn't redirecting correctly. For example. I have a index.php that is in the public directory that for now automatically redirects to the admin directory. Let me walk you through how I determine certain constants and then the two functions that I use to redirect.
There are are few contstants as defined below:
<?php
// Assign file paths to PHP constants
// __FILE__ returns the current path to this file
// dirname() returns the path to the parent directory
define("PRIVATE_PATH", dirname(__FILE__));
define("PROJECT_PATH", dirname(PRIVATE_PATH));
define("PUBLIC_PATH", PROJECT_PATH . '/public');
define("SHARED_PATH", PRIVATE_PATH . '/shared');
define("ARRAY_PATH", PRIVATE_PATH . '/arrays');
// Assign the root URL to a PHP constant
// * Do not need to include the domain
// * Use same document root as webserver
// * Can dynamically find everything in URL up to "/public"
$public_end = strpos($_SERVER['SCRIPT_NAME'], '/public') + 7;
$doc_root = substr($_SERVER['SCRIPT_NAME'], 0, $public_end);
define("WWW_ROOT", $doc_root);
Then here are the two functions in my functions.php that is included in the initialize.php (the above snippet is from initialize.php)
<?php
/**
* #param string $script_path
* #return string
*/
function url_for(string $script_path): string
{
// add the leading '/' if not present
if ($script_path[0] != '/') {
$script_path = "/" . $script_path;
}
return WWW_ROOT . $script_path;
}
/**
*
* #param string $loc
*/
function redirect_to(string $loc): void
{
header("Location: " . $loc);
exit;
}
Below is that index.php mentioned beforehand
<?php
require_once('../private/initialize.php');
header('Location: ' . url_for('admin'));
Notice that in this instance I am not using the redirect_to() function. This is the output url I am getting:
https://mydomainname.com/index./admin
This is invalid and throws a 404 http error. So decided to see what would happen if I went to what I had expected the URL to be which was
https://mydomainname.com/admin
The page is found BUT the css and js files for my project are not being found. Interestingly, I see evidence that they are included though because the css and js are dynamically loaded from the private directory through the SHARED_PATH constant. So why am I seeing this index. and how would I stop it is my basic question.

Dropbox account as Wordpress uploads folder

As I'm developing a site with the potential of holding a huge number of images I'm wondering if I can set the standard WordPress uploads folder to a Dropbox account?
And if so, how do I do that?
It would be great when I can implement it in a maner that even WordPress don't know it is a 'remote' folder. Media upload should work the same way as in a native WordPress setup.
I've read about the possibility to use another folder instead of wp-content/uploads but I could not find any info about using Dropbox for this.
Yes, you can do it. As long as you keep the same structure on Dropbox and save Dropbox shareable links as meta data for both the original file and generated sizes, a simple yet fully working setup for it would be something like the following, by using thephpleague/flysystem with their Dropbox Adapter:
Step 1
Add a file called composer.json to the root of your theme with this content:
{
"require": {
"league/flysystem": "^1",
"league/flysystem-dropbox": "^1"
}
}
Step 2
Install Composer by following these instructions
Step 3
Using the command line on your terminal/console, go to your theme directory and run:
composer install -o
Step 4
Create Dropbox App here.
I suggest that you select "App folder" as the type of access.
A directory matching the name of you app will be created on the "Apps" directory at the root of your Dropbox account; This will be your "uploads" directory on Dropbox.
Step 5
Go to your app admin page and generate a new access token.
Save the access token somewhere and also copy the "App secret"
Step 6
Add the following to your functions.php:
use League\Flysystem\AdapterInterface;
use League\Flysystem\Adapter\Local as LocalAdapter;
use League\Flysystem\Dropbox\DropboxAdapter;
use League\Flysystem\Filesystem;
use League\Flysystem\MountManager;
use Dropbox\Client as DropboxClient;
// Autoload vendors
require_once __DIR__ .'/vendor/autoload.php';
/**
* Class that will handle uploading to Dropbox
*
*/
class SO40950172Filesystem {
/**
* Contains several mounted filesystems
*
* #var League\Flysystem\MountManager object
*/
protected $filesystem;
/**
* Contains Dropbox client
*
* We need this accessible to create shareable links
*
* #var Dropbox\Client object
*/
protected $dropbox_client;
/**
* Instantiates this class
*/
public function __construct() {
// Get WordPress uploads directory info
$uploads_info = wp_upload_dir();
// Create Local filesystem
$local_adapter = new LocalAdapter($uploads_info['basedir']);
$local_fs = new Filesystem($local_adapter, [
'visibility' => AdapterInterface::VISIBILITY_PUBLIC
]);
// Create Dropbox filesystem
$this->dropbox_client = new DropboxClient($app_access_token, $app_secret);
$dropbox_adapter = new DropboxAdapter($this->dropbox_client, '/');
$dropbox_fs = new Filesystem($dropbox_adapter, [
'visibility' => AdapterInterface::VISIBILITY_PUBLIC
]);
// Set filesystem manager
$this->filesystem = new MountManager([
'local' => $local_fs,
'dropbox' => $dropbox_fs
]);
}
/**
* Uploads file to Dropbox
*
* #param string $path Path to file
* #return object Current object
*/
public function uploadToDropbox($path)
{
// Normalize path
$path = static::getNormalizedPath($path);
// Get content from the local file
$content = $this->read("local://$path");
// Push file to Dropbox
$this->put("dropbox://$path", $content);
return $this;
}
/**
* Deletes file from Dropbox
*
* #param string $path Path to file
* #return object Current object
*/
public function deleteFromDropbox($path)
{
// Normalize path
$path = static::getNormalizedPath($path);
// Delete file from Dropbox
$this->delete("dropbox://$path");
return $this;
}
/**
* Returns the unique identifier path section of a Dropbox URL
*
* #param string $path Path to file
* #return string Dropbox URL unique identifier
*/
public function getDropboxUrlID($path)
{
// Normalize path
$path = static::getNormalizedPath($path);
// Get unique link
$url = $this->dropbox_client->createShareableLink("/$path");
// Parse URL to retrive its path
$url_info = parse_url($url);
$url_path = $url_info['path'];
// Remove "s/" section and file name from the URL path
$id = str_replace(['s/', basename($path)], '', $url_path);
// Return Dropbox unique identifier for this file URL
return trim($id, '/');
}
/**
* Returns clean & relative paths
*
* #param string $path Raw path
* #return string Parsed path
*/
public static function getNormalizedPath($path)
{
// Get WordPress uploads directory info
$uploads_info = wp_upload_dir();
// Remove uploads base path so that we end up
// with the "/YYYY/MM/filename.extension" format
$path = str_replace($uploads_info['basedir'], '', $path);
// Remove uploads base url so that we end up
// with the "/YYYY/MM/filename.extension" format
$path = str_replace($uploads_info['baseurl'], '', $path);
// Remove forward slashes on both ends
$path = trim($path, '/');
// Return path
return $path;
}
/**
* Making sure all calls go to $this->filesystem
*
* #param string $name Method name
* #param array $args Method arguments
* #return mixed
*/
public function __call($name, array $args)
{
if (method_exists($this->filesystem, $name))
throw new \Exception("\League\Flysystem\MountManager doesn't have \"$name\" method");
return call_user_func_array([$this->filesystem, $name], $args);
}
}
// Manipulate media URLs sitewide
add_filter('wp_get_attachment_url', 'so_40950172_get_dropbox_url', 9, 2);
function so_40950172_get_dropbox_url($absolute_url, $post_id) {
// Get normalized path
$path = SO40950172Filesystem::getNormalizedPath($absolute_url);
// Get only the filename
$path = basename($path);
// Get Dropbox URL unique ID
$id = get_post_meta($post_id, 'dropbox_id_'. $path, true);
// Return absolute URL
return $id ? "https://dl.dropboxusercontent.com/s/$id/$path/?dl=0" : $path;
}
// Upload new and updated files to Dropbox
add_filter('wp_update_attachment_metadata', 'so_40950172_upload_to_dropbox', 9, 2);
function so_40950172_upload_to_dropbox($data, $post_id) {
// Get filesystem
$fs = new SO40950172Filesystem();
// Upload original file to Dropbox
$fs->uploadToDropbox($data['file']);
// Add Dropbox URL unique ID to meta data
add_post_meta($post_id, 'dropbox_id_'. basename($data['file']), $fs->getDropboxUrlID($data['file']));
// Upload intermediate image sizes
if (isset($data['sizes']) && $data['sizes']) {
// Get year and month prefix (e.g /YYYY/MM) from original file
$base_path = dirname($data['file']);
// Loop through all sizes
foreach ($data['sizes'] as $size_name => $size_data) {
// Set path for current size
$size_path = $base_path .'/'. $size_data['file'];
// Upload size to Dropbox
$fs->uploadToDropbox($size_path);
// Add Dropbox URL unique ID to meta data
add_post_meta($post_id, 'dropbox_id_'. basename($size_path), $fs->getDropboxUrlID($size_path));
}
}
return $data;
}
// Delete Dropbox file
add_filter('wp_delete_file', 'so_40950172_delete_dropbox_file');
function so_40950172_delete_dropbox_file($absolute_path) {
// Get filesystem
$fs = new SO40950172Filesystem();
// Delete file from Dropbox
$fs->deleteFromDropbox($absolute_path);
}
Step 7
On the code your just pasted into functions.php:
Replace $app_access_token with the Dropbox app access token you generated
Replace $app_secret with the Dropbox app secret
NOTES
The original file and generated sizes will also be saved locally, but you don't need to worry about them. You can even delete the local file after confirmation of a successful upload, if you want and/or care about disk space.
I also tested the built-in image editor and it worked without any issues.
If you ever need to move things on the Dropbox side and since there is no info saved on the database (this is just fine), all you need to do is to update the functions above.
Apparently you can mirror the WordPress structure on Dropbox, but you can't simply link to them using a base URL and the WordPress uploads structure to get the URLs, you really need to get the shareable link for each original file and generated sizes and store something about them as metadata. On the code above I chose to only store the unique part of the URL as metadata, which really is the only unique thing about them.
I know this is off-topic, but I would recommend either AWS S3 or Google Cloud Storage because you can access your files with the exact same file structure you have on Dropbox. No need to save anything as meta data.
that's an interesting idea, but WordPress uses DB relations in order to manage the uploaded images - so i think you must handle the images through the media uploader. TL:DR; - you can't.

Third_party Views Path(s) Check in CodeIgniter 3

I'm trying to check if a view file really exists according to the third_party paths loaded
Normally, I'd check if a view exists with is_file(APPPATH.'/views/folder/'.$view)
I can retrieve every loaded third_party paths with get_package_paths (thanks to the comment of Tpojka) and then check in their folder views if the file exists,
but I was hoping for a 'direct' check, as if the ->view function would return false instead of redirecting to an error page
$html = $this->load->view($tpl,'',TRUE) ? $this->load->view($tpl,'',TRUE) : $another_template;
Though I realize there might be no other solutions that adding this manual check with a loop through the loaded paths and hide it in a CI_Load Class extension (application/core/MY_Loader) to give the apparance of a direct check in the controller:
EDIT: This is a bad idea, cause view() may return false to CI function that might not be designed for
class MY_Loader extends CI_Lodaer{
public function __construct() {
parent::__construct();
}
public function view($view, $vars = array(), $return = FALSE)
{
foreach( $this->get_package_paths( TRUE ) as $path )
{
// this will only retrieve html from the first file found
if( is_file( $path."/views/".$view ) ) return parent::view($view, $vars, $return);
}
// if no match
return false;
}
}
What I find annoying is that load->view already makes a check through the paths, so this solution will add a second check and increase server consumption..
Well in the end I choose this lukewarm solution :
Instead of extending the function view() for it to return false (and having to deal with it through CI then after !) , I just made a function is_view() in the application/core/MY_Loader.php
I'm not sure MY_Loader is the correct place to place such a function, but so far it did the trick for me ...
(thx Tpojka for the indication)
in application/core/MY_Loader.php
/**
* is_view
*
* Check if a view exists or not through the loaded paths
*
* #param string $view The relative path of the file
*
* #return string|bool string containing the path if file exists
* false if file is not found
*/
public function is_view($view)
{
// ! BEWARE $path contains a beginning trailing slash !
foreach( $this->get_package_paths( TRUE ) as $path )
{
// set path, check if extension 'php'
// (would be better using the constant/var defined for file extension of course)
$path_file = ( strpos($view,'.php') === false ) ? $path."views/".$view.'.php' : $path."views/".$view ;
// this will return the path at first match found
if( is_file( $path_file ) ) return $path."views/";
}
// if no match
return false;
}
and in application/controllers/Welcome.php
$view = "frames/my_html.php";
/*
* the view file should be in
* application/third_party/myapp/views/frames/my_html.php
*
* so far, if the file does not exists, and we try
* $this->load->view($view) will redirect to an error page
*/
// check if view exists and retrieve path
if($possible_path = $this->load->is_view($view))
{
//set the data array
$data = array("view_path"=>$possible_path);
// load the view knowing it exists
$this->load->view($view,$data)
}
else echo "No Template for this frame in any Paths !";
and of course in the view
<h1>My Frame</h1>
<p>
The path of this file is <=?$view_path?>
</p>

Search through a directory in PHP, beginning at the end [duplicate]

This question already has answers here:
How to use return inside a recursive function in PHP
(4 answers)
Closed 9 months ago.
I have this PHP code, but I have only written one recursive function before. I want to begin in a particular subdirectory and check for the presence of a file. If it doesn't exist in the directory I want to check the parent folder for the file and then recursively go on until I either don't find the file and return "" or find the file and return its contents. Here is my function:
/**
* Recursive function to get sidebar from path and look up tree until we either
* find it or return blank.
*/
class MyClass{
public function findSidebarFromPath($path){
if(file_exists($_SERVER["DOCUMENT_ROOT"] . implode("/", $path) . "/sidebar.html")){
return file_get_contents($_SERVER["DOCUMENT_ROOT"] . implode("/", $path) . "/sidebar.html");
} else {
if(count($path) > 0){
# Pop element off end of array.
$discard = array_pop($path);
$this->findSidebarFromPath($path);
} else {
return "";
}
}
}
}
So when I call it, I do this:
$myclass = new MyClass();
$myclass->findSidebarFromPath(array("segment1", "segment2", "segment3"));
Let's say the file I am trying to find is in a directory called "segment2". The function never finds it. The array_pop function doesn't pop off the element off the end of the array and then call the findSidebarFromPath function.
If you write this as a standalone function, it will probably be more useful to you in other areas. After we understand how it works, we'll show you how you can add a public function to your class that can utilize it.
/**
* #param $root string - the shallowest path to search in
* #param $path string - the deepest path to search; this gets appended to $root
* #param $filename string - the file to search for
* #return mixed - string file contents or false if no file is found
*/
function findFile($root, $path, $filename) {
// create auxiliary function that takes args in format we want
$findFileAux = function($cwd, $filename) use (&$findFileAux, $root) {
// resolve the complete filename
$file = "{$cwd}/{$filename}";
// if the file exists, return the contents
if (file_exists($file)) return file_get_contents($file);
// if the cwd has already reached the root, do not go up a directory; return false instead
if ($cwd === $root) return false;
// otherwise check the above directory
return $findFileAux(dirname($cwd), $filename);
};
// starting checking for the file at the deepest segment
return $findFileAux("{$root}/{$path}", $filename);
}
Check the example output on ideone.
So here's how to use it
findFile($_SERVER["DOCUMENT_ROOT"], "foo/bar/qux", "sidebar.html");
Here's how you would integrate it with your class. Notice that this public function has the same API as in your original code
class MyClass {
/**
* #param $path array - path segments to search in
* #return mixed - string file contents or false if sidebar is not found
*/
public function findSidebarFromPath($path) {
// Here we call upon the function we wrote above
// making sure to `join` the path segments into a string
return findFile($_SERVER["DOCUMENT_ROOT"], join("/", $path), "sidebar.html";
}
}
Additional explanation
If $_SERVER["DOCUMENT_ROOT"] is /var/www...
Check /var/www/foo/bar/qux/sidebar.html, return if exists
Check /var/www/foo/bar/sidebar.html, return if exists
Check /var/www/foo/sidebar.html, return if exists
Check /var/www/sidebar.html, return if exists
Because we got to the root (/var/www) no further searches will happen
return false if sidebar.html did not exist in any of the above
Here's the same function with the explanatory comments removed
/**
* #param $root string - the shallowest path to search in
* #param $path string - the deepest path to search; this gets appended to $root
* #param $filename string - the file to search for
* #return mixed - string file contents or false if no file is found
*/
function findFile($root, $path, $filename) {
$findFileAux = function($cwd, $filename) use (&$findFileAux, $root) {
$file = "{$cwd}/{$filename}";
if (file_exists($file)) return file_get_contents($file);
if ($cwd === $root) return false;
return $findFileAux(dirname($cwd), $filename);
};
return $findFileAux("{$root}/{$path}", $filename);
}
You might also want to consider using DIRECTORY_SEPARATOR instead of the hard-coded "/" so that this code could be used reliably on a variety of platforms.

vfsstream paths and realpath

I'm experimenting with vfsStream for unit testing filesystem interactions and have very quickly run into a major hurdle. One of the validation checks the code under test does is execute realpath() on a supplied input path to test that it's an actual path and not nonsense. However, realpath always fails on a vfsstream path.
The following code demonstrates the problem outside of any particular class.
$content = "It rubs the lotion on its skin or else it gets the hose again";
$url = vfsStream::url ('test/text.txt');
file_put_contents ($url, $content);
var_dump ($url);
var_dump (realpath ($url));
var_dump (file_get_contents ($url));
The output is as follows:
string(27) "vfs://FileClassMap/text.txt"
bool(false)
string(61) "It rubs the lotion on its skin or else it gets the hose again"
Obviously the vfsStream created the file and wrote the given content to it, but I can't verify that the path to it is correct with realpath. As realpath is being used inside the actual code I need a way of working about this.
I really don't think removing realpath is a sensible approach because it performs an important function inside the code, and eliminating an important check just to make the code testable seems a pretty poor solution. I could also put an if around the test to make it possible to disable it for testing purposes, but again I don't think that's a good idea either. Also I'd hate to have to do that at every point in the code where I might make a call to realpath (). The third option would be to set up a RAM disk for filesystem unit tests, but that's not ideal either. You have to clean up after yourself (which is what vfsstream is supposed to help you avoid the need for) and how to actually do it will differ from OS to OS, so the unit tests would cease to be OS agnostic.
So is there a way to get a vfsstream path in a format that actually works with realpath?
For completeness, the following is the code fragment from the class I'm trying to actually test.
if (($fullPath = realpath ($unvalidatedPath))
&& (is_file ($fullPath))
&& (is_writable ($fullPath))) {
A refactoring to the following (as per potential solution 2) allows me to test with vfsStream, but I think it could be problematic in production:
// If we can get a canonical path then do so (realpath can fail on URLs, stream wrappers, etc)
$fullPath = realpath ($unvalidatedPath);
if (false === $fullPath) {
$fullPath = $unvalidatedPath;
}
if ((is_file ($fullPath))
&& (is_writable ($fullPath))) {
If you use namespaces you can override the realpath function only in the test class. I always use canonical paths in my vfsStream testcases, beause i don't want to test the realpath() function itself.
namespace my\namespace;
/**
* Override realpath() in current namespace for testing
*
* #param string $path the file path
*
* #return string
*/
function realpath($path)
{
return $path;
}
Good described here: http://www.schmengler-se.de/en/2011/03/php-mocking-built-in-functions-like-time-in-unit-tests/
I opened a bug with an implementation of Sebkrueger's method on vfsStream: https://github.com/bovigo/vfsStream/issues/207
Waiting for their feedback, here is my working realpath():
/**
* This function overrides the native realpath($url) function, removing
* all the "..", ".", "///" of an url. Contrary to the native one,
*
* #param string $url
* #param string|bool The cleaned url or false if it doesn't exist
*/
function realpath(string $url)
{
preg_match("|^(\w+://)?(/)?(.*)$|", $url, $matches);
$protocol = $matches[1];
$root = $matches[2];
$rest = $matches[3];
$split = preg_split("|/|", $rest);
$cleaned = [];
foreach ($split as $item) {
if ($item === '.' || $item === '') {
// If it's a ./ then it's nothing (just that dir) so don't add/delete anything
} elseif ($item === '..') {
// Remove the last item added since .. negates it.
$removed = array_pop($cleaned);
} else {
$cleaned[] = $item;
}
}
$cleaned = $protocol.$root.implode('/', $cleaned);
return file_exists($cleaned) ? $cleaned : false;
}

Categories