Note: While the issue occurred trying to solve a WordPress problem, the problem being faced right now (see "Current Approach", below) is clearly a PHP issue, which is why it's been posted here.
TL;DR:
I'm dynamically generating PHP code in a file and including that file after it's finished being written to, however PHP cannot call functions from inside the file—even when including it directly without making any changes.
Preface
I've been working on a system for dynamically generating shortcodes in WordPress, and have been running into one serious roadblock along the way: Registering the functions for the shortcodes.
Long story short is that we've got a list of options stored in an array similar to the following:
$array = array(
0 => array(
'code' => 'my-shortcode',
'replacement' => 'replacement_directives_here'
)
);
I've already got a system that processes the codes and sends generates the proper output based on the directives. It's getting the call to add_shortcode($shortcode, $callback) to work.
My first strategy was to use anonymous functions in a manner similar to the following:
foreach ($array as $code => $directions) {
$GLOBALS['my_shortcode_output'] = my_shortcode_process($directions);
add_shortcode($code, function() { return $GLOBALS['my_shortcode_output']; });
}
However this ended up with successive directives overwriting each other due to the fluctuating content of the global, so I decided to try something different...
Current Approach
As a workaround to having the information I need constantly slipping just out of reach, I decided to try something different:
Create a file within my plugin
Write out the PHP code for the functions I wanted to include so that they were guaranteed to generate the right output
Include the file after the fclose() call
Register the shortcodes, referencing the functions in the new file
The code for generating the file looks something like the following:
$file = fopen(PATH_TO_FILE, 'w'); // Open for writing only, truncate file on opening
fwrite($file, "<?php\n\n"); // Using double quotes so newlines and escapes don't get counted as literals
// foreach through list
foreach ($shortcode_list as $shortcode) {
if ($shortcode['code'] != '') {
// Append new function with a dynamic name (e.g. $shortcode[code]._sc') to dynamic_shortcodes.php
// Function should consist of: return my_shortcode_process($shortcode['replacement']);
// Hard-coding so we don't get frozen if information gets changed
$new_function = "\tfunction str_replace('-', '_', $shortcode['code'])."_sc() {\n\t\treturn my_shortcode_process('".$shortcode['replacement']."');\n\t}\n\n";
fwrite($file, $new_function);
// Add function name to $shortcode_functions array, keyed on the shortcode
$shortcode_functions[$shortcode['code']] = str_replace('-', '_', $shortcode['code']).'_sc';
}
}
fclose($file); // Close the file, since we are done writing to it
touch(PATH_TO_FILE); // Ensure the file's modification time is updated
After that it's just a simple loop to register them:
foreach ($shortcode_functions as $shortcode => $callback) {
add_shortcode($shortcode, $callback);
}
When I reload and run everything, however, I get the following:
Notice: do_shortcode_tag was called incorrectly. Attempting to parse a shortcode without a valid callback: [SHORTCODE HERE]. Please see Debugging in WordPress for more information.
I've downloaded the file and verified its contents—everything checks out in PHPstorm. It's properly formatted, there are no syntax errors, and the functions it depends upon are already loaded and working fine.
Even skipping the whole process and just directly including the file and then calling one of the functions inside it isn't working. It's like the file has been blackballed. At the same time, however, neither include() nor require() produce any errors.
Is this a caching issue, perhaps?
Honestly, I'm totally unable to understand what you're trying to do (or even why) but PHP doesn't have any problem with dynamically generated includes. This works fine:
<?php
$file = __DIR__ . '/foo-' . mt_rand(0, PHP_INT_MAX) . '.php';
$code = '<?php
function foo() {
echo "Hello, World!";
};
';
file_put_contents($file, $code);
require $file;
foo();
However, you're apparently trying to execute something like this:
function str_replace('-', '_', $shortcode['code'])."_sc(){
}
That's a blatant syntax error either in included files on in the main script. If you follow the Please see Debugging in WordPress for more information instructions you'll possibly find something similar to this:
Parse error: syntax error, unexpected ''-'' (T_CONSTANT_ENCAPSED_STRING), expecting variable (T_VARIABLE)
A syntax for variable functions that works could be:
$name = str_replace('-', '_', $shortcode['code']) . '_sc';
$name = function (){
};
Related
I have a configuration file which, amongst other things, defines a whole bunch of values and callables. I'm writing a development tool which needs to update some of the definitions in this file. I can't think of a way to do this which isn't just reading the file and writing my string at a certain point defined by a comment within the file.
Imagine my file looks like this:
<?php
$things = [
'a_thing' => 'some value',
'callable_thing' => function() {
return SomeClass();
}
// END THINGS
];
I'd like to insert a new array key into this array (the value of which might be any valid option), in alphabetical order. Is this possible, using Reflection maybe? Second best would be to place a specially formatted comment (i.e the "// END THINGS" comment) as a "target" and insert before it, but I can't help but feel that's pretty hacky, and doesn't satisfy the alphabetical requirement.
I'm aware of the implications of self-mutating code. This is a developer tool and would only be used locally to automate common file->create->update tasks when setting up new projects.
You could use the var_export() function to print the value of a variable in a way that can be evaluated. So you would load the file with include($filename);, and modify the contents of $things. Then you can update the file with:
$contents = '$things = ' . var_export($things, true) . ';';
file_put_contents($filename, $contents);
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 7 years ago.
Improve this question
In my code there are 3 sections. Top and bottom section must have to execute. Middle portion have to hide if input code is wrong.
This is my code:
//correct code section. Must execute
echo "must show top";
//may contain wrong code. But this portion should not affect on lower
portion code
ob_start();
// sample wrong code
function jk($i){};
function jk($i){ echo $i; }';
ob_end_clean();
//correct code section. Must execute
echo "must show bottom";
Current output:
Fatal error: Cannot redeclare jk() (previously ......
Expected output:
must show top must show bottom or must show top
Fatal error: Cannot redeclare jk() (previously ......
must show bottom
You can't do that in this way, because the PHP file is compiled (to bytecode, but whatever) before any part of it is executed.
You can handle minor errors by wrapping the middle part in try/catch, but fatal errors will still make the whole script not work.
So what you can do is to put the middle code somewhere else, and then access it from outside the whole process scope. This is possibly tricky if the two parts need to exchange information (e.g. some variables).
The simplest way to do that is to save the code into a temporary file, then access it from within PHP by using URL get_file_contents:
// Construct PHP tag -- hiding it from editor :-)
$prolog = '<'; $prolog .= '?'; $prolog .= "php\n";
// prolog code contains PHP code, but here we treat is as text,
// so all $'s must be escaped. Same goes for epilog.
$prolog .= <<<PROLOG
// This is common code to make what follows work.
// We can pass any data to this script by creating a *second* file
// and passing the name of this second file via GET to avoid POST
// size limits.
// To avoid arbitrary inclusion (just in case), we could check that
// the file name thus passes conforms to a given syntax, e.g. 32
// random hex characters, no URLs or /etc/passwd or funny stuff.
if (isset(\$_GET['envfile'])) {
\$name = basename(\$_GET['envfile']) . '.txt';
\$data = unserialize(file_get_contents(\$name));
unlink(\$name);
} else {
\$data = null;
}
PROLOG;
$epilog = <<<EPILOG
// The code is now hopefully loaded and defines a main()
// function.
if (!function_exists('main')) {
die("-ERR: no main function");
}
try {
die(json_encode(main(\$data)));
} catch (\Exception \$err) {
die("-ERR: main failed: {\$err->getMessage()}");
}
EPILOG;
$unique = uniqid();
// "special dir" is NOT available from THIS site, and is the webroot of another "sandbox" web site
$physical = "./special-dir/{$unique}.php";
// file must contain a full PHP script with tags and all
file_put_contents($physical, $prolog . $code . $epilog);
// Now ask the file to the web server with its "sandbox" personality
$response = file_get_contents("http://sandbox/{$unique}.php");
The above uses a virtual host which should be only accessible from localhost itself, and which (in addition to careful security: sensitive functions disabled, no access outside its own webroot, ...) should have full debugging information and error reporting turned on.
So if you get a PHP error page, you know that an error occurred (and you might even parse it). Otherwise, the script might return e.g. a JSON-encoded variable object which you could import:
if (0 === strpos($response, '-ERR')) {
// $response is a simple error message
}
$package = json_decode($response, true);
if (false === $package) {
// $response is probably a fatal error HTML page
}
// Do something with $package.
So to recap:
get the code from user
mix it with appropriate prolog/epilog stubs to e.g. prime some variables to make them available to the "child" script, and return data as JSON
save the resulting code into a temporary file within the webroot of
a secured "sandbox" website with full debug
invoke the sandbox URL to have a second PHP interpreter spawn and look at the code
interpret the results
NOTE: in PHP 7, the new code parse tree approach could perhaps allow you to validate the code directly, without the need of saving it and sending to a new PHP interpreter. Then if it's OK you could eval() it directly, but take care that doing so executes the code in the current script's scope, which has higher privileges. You are quite likely to have your sandbox pwn3d and/or abused in really short order.
This function is returning the content of the file rather the result of fetch_link_settings_overide() within it.
The issue is not with the overide function as after the initial error I commented out my modification just to be sure it wasn't something I had done there.
function fetch_link_settings(){
include( plugins_url()."/plugin-child/plugin_overrides.php");
return fetch_link_settings_override();
}
Adding the content of the derived function plugin-child/plugin_overrides.php as we are not getting anywhere currently.
function fetch_link_settings_override(){
global $post;
// If the destination url is set by the user, use that. Otherwise, use the permalink
$destination_url = get_post_meta($post->ID, '_promo_slider_url', true);
// ASAdd additional place to look in the case of the post being via the PODS advert track
if( ! $destination_url )
$destination_url = get_post_meta($post->ID, 'okd_advert_link', true);
if( ! $destination_url )
$destination_url = get_permalink($post->ID);
// If the target attribute is set by the user, use that. Otherwise, set it to _self
$target = get_post_meta($post->ID, '_promo_slider_target', true);
if( ! $target ) $target = '_self';
// Setup the disable links variable
$disable_links = get_post_meta($post->ID, '_promo_slider_disable_links', true);
return compact('destination_url', 'target', 'disable_links');
}
You write this:
include( plugins_url()."/plugin-child/plugin_overides.php");
Why is plugins_url() there? The include function is strictly based on the file system:
The `include` statement includes and evaluates the specified file.
As explained in the WordPress docs, the plugins_url() would give you the full web URL which is 100% different than the file system WordPress is installed on:
Retrieves the absolute URL to the plugins directory (without the
trailing slash) or, when using the $path argument, to a specific file
under that directory.
So perhaps it should be like this:
include("/plugin-child/plugin_overides.php");
Or perhaps you need the plugin_dir_path()?
include(plugin_dir_path( __FILE__ ) . "/plugin-child/plugin_overides.php");
But that seems wrong. Where would /plugin-child/plugin_overides.php? Try doing this:
include("/full/path/to/wordpress/and/this/plugin-child/plugin_overides.php");
Just replace /full/path/to/wordpress/and/this/ with the actual file system path to /plugin-child/plugin_overides.php.
EDIT: Since the original poster is persistent in using plugins_url() despite all of the suggestions otherwise, here is my detailed response:
…you said “you cannot load raw functions via a URL with include” well
this is not relevant because even if I add $some_var = 'smith'; as the
first statement in the included file, it is not visible in the
function using the include.
Apologies. Functions, classes, strings, constants… Just about anything that you want to be raw, unprocessed PHP will simply not be passed via an http:// or https:// URL because Apache will parse the PHP instructions & simply return the output of that file and not the raw, unprocessed contents of the PHP in that file.
Additionally the original poster contents the following:
You can’t help me because what you are saying does not make sense or
you are not explaining yourself adequately. Look at these examples:
include realpath(dirname(FILE) . "/" . "relative_path");
include("data://text/plain;base64,".base64_encode($content));
include("data://text/plain,".urlencode($content));
All taken from the official PHP documentation. They all use
functions returning components that are concatenated with the rest of
the url. I also tried this typing the filepath explicitly and the
result is the same.
The examples cited are as follows:
include realpath(dirname(FILE) . "/" . "relative_path");
This is a filesystem level include which is the most common way PHP files are included into other files.
include("data://text/plain;base64,".base64_encode($content));
include("data://text/plain,".urlencode($content));
These are both data URLs. Not http or https. So again when you use plugins_url() what you are getting is a full http:// or https:// URL in which Apache parses the PHP instructions & simply return the output of that file and not the raw, unprocessed contents of the PHP in that file. Or as very clearly explained in the PHP documentation you are linking to; emphasis mine:
If "URL include wrappers" are enabled in PHP, you can specify the file
to be included using a URL (via HTTP or other supported wrapper - see
Supported Protocols and Wrappers for a list of protocols) instead of a
local pathname. If the target server interprets the target file as PHP
code, variables may be passed to the included file using a URL request
string as used with HTTP GET. This is not strictly speaking the same
thing as including the file and having it inherit the parent file's
variable scope; the script is actually being run on the remote server
and the result is then being included into the local script.
Going back to your example, you say now the contents of plugin_overides.php is $some_var = 'smith';. How exactly? If it is a PHP file like this:
<?php
$some_var = 'smith';
?>
When you call that file via a URL generated by the following code:
include(plugins_url() . "/plugin-child/plugin_overrides.php");
Assuming your website is http://some.cool.website/ the you are basically making a call like this:
http://some.cool.website/plugin-child/plugin_overides.php
So the output of plugin_overides.php would be 100% blank. If you wanted to get output of that file, you could do the following:
<?php
$some_var = 'smith';
echo $some_var;
?>
And that would return smith. Meaning the absolute ONLY output you would get from that call is pure text. Nothing else.
Now I see you actually have posted the contents of plugin_overides.php. My example explanation above is still apt, but still a basic question. This is your function; just the interface & return for example:
function fetch_link_settings_override(){
// Other code removed. Just a structural illustration for now.
return compact('destination_url', 'target', 'disable_links');
}
Do you actually call fetch_link_settings_override() in plugin_overides.php when it runs? Well, if that function does not run, then there is 100% no way you will ever get any output. But assuming good faith, look at your return statement here:
return compact('destination_url', 'target', 'disable_links');
If you are returning compact, then you are returning an array. You cannot simply return a bare array as a URL call like this http://some.cool.website/plugin-child/plugin_overides.php. The output at most would be simply the word Array.
If the goal is to take that array & do something, then you should use json_encode in fetch_link_settings_override and then use json_decode on the receiving side of that. So the return statement would be something like this:
return json_encode(compact('destination_url', 'target', 'disable_links'));
I'm trying to use PHP variables into echoed file, and couldn't get where is a trouble at first using that script:
$head = new mod_head("head.php");
$id="ASDSSgdfsfsdfS";
echo $head;
mod_head class:
class mod_head
{
private $out="";
function __construct($arg)
{
$this->out=$this->parts($arg);
}
public function __toString()
{
return $this->out;
}
private function parts($file)
{
return fread(#fopen(PATH . "parts/".$file, 'r'), filesize(PATH . "parts/".$file));
}
}
and the file is "head.php"
<h1><center style="background:orange; border-radius:15px;">LOGO</center></h1>
<br><?php print_r($id)?>
<div>BANNER <div>$id <?php echo $id ;?></div></div>
i dont want to create global vars, why it doest echo $id var?
First, you're going to need to parse the file, not just read it. The second problem you'll have is a scope issue. $id is outside of the scope of the parts() function. In order to return the contents of the required file instead of just printing it I've used the output control functions
Try changing your parts function to this:
private function parts($file)
{
ob_start();
require(PATH . "parts/".$file);
$output = ob_get_contents();
ob_end_clean();
return $output;
}
To fix the scope issue try changing $id="ASDSSgdfsfsdfS"; to $head->id = "ASDSSgdfsfsdfS";, then change head.php to be the following:
<h1><center style="background:orange; border-radius:15px;">LOGO</center></h1>
<br><?php print_r($this->id)?>
<div>BANNER <div>$id <?php echo $this->id ;?></div></div>
Simply reading a file with fread will not parse any PHP contained inside. Perhaps you are looking for something like:
http://php.net/manual/en/function.require.php
Using require() is basically like copying and pasting the required file directly where your require() statement is. This means that the required file would only be able to use variables that are within the scope of where the require() statement is.
Because you are reading the contents of head.php and echoing them verbatim; nowhere do you make PHP compile and run that file as code. You could do that using include('head.php'), but that would not work blindly because you also have to make sure that $id is in scope at the point you do the include.
However that's not as easy as it sounds because it is not possible to automatically "pack the whole local scope" for later use because automatically implies code inside a function, and the very act of calling that function causes the scope to change.
My thought is that you are probably using mod_head is passing in a php file so it can be used as a view, right?
if thats the case, usually there is also a way to include a variable so that way it exists in the other file, head.php.. since there isn't one, you'd need to create a method for that. and then make that fread an include or require instead.
If not, and you're simply loading the file, the other comments about fread not parsing is totally correct and you will not be able to simply access $id from the other file.
I am trying to migrate my site to PHP and use a WP theme on my site with out modifying the theme too much. So my problem is that I want to create a functions.php file like in Wordpress that contains some code that can be called to load the header and footer of my sites theme from each page. Also I want to keep some constants in another file that will be used in my site for loading files from my sub-domains, getting the current year, etc. I have a basic get_file() function for loading files from my static content domain, But when I try to run it I get this:
Parse error: syntax error, unexpected T_FUNCTION in /home/s0urc3/public_html/includes/functions.php on line 4
Here are the contents of my functions.php and my constants.php:
functions.php
<?php
include('constants.php')
/*Gets a file from the domain http://files01.s0urc3.ismywebsite.com/*/
function get_file($file)
{
FILE_ROOT + $file
}
get_file(images/bg.jpg)
?>
constants.php
<?php
/* CONSTANTS*/
define(FILE_ROOT, "http://files01.s0urc3.ismywebsite.com/")
define(HOME, "http://s0urc3.ismywebsite.com/")
define(BLOG_HOME, "http://blog.s0urc3.ismywebsite.com/")
define(FORUMS_HOME, "http://forums.s0urc3.ismywebsite.com/")
define(YEAR, getdate(year))
/*define(FILE_ROOT, "http://files01.s0urc3.ismywebsite.com/")*/
?>
Any and all help would be much appreciated.
You have virtually no lines free of syntax errors. You will never get anywhere muddling your way forward this way. Find a tutorial and learn PHP before trying to port your code.
Specifically:
In PHP, as in other C-style languages, your statements must end with semicolons:
// bad; missing semi-colon
include('constants.php')
// good:
include('constants.php');
Your uses of define are wrong. The symbol to define must be passed to define() as a string before it can be used as a symbol:
// bad; FILE_ROOT not yet defined, missing semi-colon
define(FILE_ROOT, "http://files01.s0urc3.ismywebsite.com/")
// good:
define('FILE_ROOT', "http://files01.s0urc3.ismywebsite.com/");
Strings must be enclosed in either single quotes or double quotes:
// bad; missing quotes around string, missing semi-colon
get_file(images/bg.jpg)
// good:
get_file('images/bg.jpg');
Values must be explicitly returned from functions via the return keyword, and strings are concatenated using the dot . operator:
// bad; Perform integer addition, doesn't return, no semi-colon
FILE_ROOT + $file
// good:
return FILE_ROOT . $file;
And a note on style: You've commented get_file as 'getting a file', which it definitely doesn't do. All it does is return the fully-qualified URL of the relative URL you pass it. Your function names and comments should strive to clearly tell you what they do. In the case of such a short utility function, I'd go for something very short:
// Return the fully-qualified URL for the given file
function url_for($file)
{
return FILE_ROOT . $file
}
In PHP most lines end with a semicolon ';' This is what is giving those errors.
// should be
include('constants.php');
get_file('images/bg.jpg');
// etc.....
Your Syntax makes no sense, look what your function does, it would be the same as just writing:
function get_file($file)
{
"http://files01.s0urc3.ismywebsite.com/" + $file
}
Thats not PHP or anything, what exactly is it you want to do? If you want the complete filepath, it would be :
function get_file($file)
{
return FILE_ROOT . $file;
}
// returns: http://files01.s0urc3.ismywebsite.com/images/bg.jpg
You should have 2 seperate files, constants and functions
Put data in each accordingly and the simply include them and use
require_once 'constants.php';
require_once 'functions.php';
within functions the example layout should be like so:
function a()
{
//...
}
function b()
{
//...
}
function c()
{
//...
}
followed by usages:
if(!user_session_active())
{
location('home');
}
get_file('some_file.ext');
you forgot semicolons at the end of every line where you have to use one. (after the defines, after the include, after the function call...)
string concatenation in php works bu using ., not +