WordPress: How to only load scripts if widget is displayed

I have written a few WordPress plugins to which I keep adding functionality. Very often the number of scripts and styles I need to load to support these enhancements keep growing. Usually they are still not so large. But if you have pages where the widgets are not displayed, you’re just loading scripts and style that are actually not required and thus slowing down the page load without any additional benefit.

So I was looking for a way to only load the scripts and styles I require for one of my plugin when the widget provided by this plugin is actually displayed. Pretty quickly I came across the WordPress built-in is_active_widget Widget function. This is the description of this function in the WordPress codex:

This Conditional Tag checks whether widget is displayed on the front-end.

Sounds like exactly what I was looking for ! Unfortunately the description of the function is wrong. As the name of the function says, it tells you whether a widget is active, not whether it is displayed on this particular page (or on any page for that matter). An active widget is a widget which has been instantiated in a sidebar. This means that you created an instance of the widget in a sidebar but it doesn’t tell you whether this sidebar is displayed anywhere and even less whether this sidebar is displayed in the current page.

So you will probably find some suggestions to handle the problem using something like:

function check_widget() {
    if(is_active_widget( '', '', 'my_widget_id')){
        wp_enqueue_script(...);

    }
}
add_action('wp_footer', 'check_widget');

The only case I can think of where this will effectively prevent your scripts from being unnecessarily loaded is if you do not use this widget in any sidebar. Of course, if the sole purpose of your plugin is to provide this widget, your users could just deactivate it instead of relying on this method…

But if the widget exists in a sidebar and a full-width page (without sidebar) is being displayed, it will still load the scripts.

On the other hand, if you allow your users to display you widgets only outside of a sidebar by using a short code or by calling a PHP function displaying the same contents as the widget, the scripts will not be loaded and it will not work anymore…

So the solution I went for is a different one. This might not work for all plugins but for the one I was updating, it works perfectly. All you need to do is not to enqueue the scripts in wp_head or before it, but to enqueue them when displaying something (whether as a widget or a short code).

I have done the following:

  1. Add a plugin setting called do-not-load-scripts which allows the administrator to defined that the scripts should not be loaded using the wp_enqueue_scripts hook but only when a widget is being displayed.
  2. In my wp_enqueue_scripts hook, I check the value of this setting. If it’s set, I enqueue scripts and styles if is_admin() returns true.
  3. In the widget() function of my widget, I check the value of the setting. If set, I enqueue the scripts and styles.

If multiple instances of the widget are displayed. the enqueue functions will be called multiple times but it will still only output the scripts and styles only once. Of course you could also set a global variable to only enqueue once but I doubt this would have much of a positive impact on performance or load.

Since my short code or the PHP function I provide basically just call the widget() function with some parameters, this will handle all cases where the widget is displayed whether in a sidebar or not.

When using this method, you need to make sure that your widget doesn’t rely on the scripts and styles being loaded in header (because they will be loaded in the footer).

The code for the wp_enqueue_scripts hook looks like this:

if ( is_admin() ) {
	wp_enqueue_style( ... );
	wp_enqueue_script( ... );
} elseif (<setting NOT set by admin>) {
	wp_enqueue_style( ... );
	wp_enqueue_script( ... );
}

And the code in the widget() function like this:

if (<setting set by admin>) {
	wp_enqueue_style( ... );
	wp_enqueue_script( ... );
}

 

Mac OS X: PHP Notice: Use of undefined constant MCRYPT_RIJNDAEL_128

My setup:

  • OS: Mac OS X Yosemite (10.10.1)
  • Using Homebrew
  • PHP: 5.5.14

I’ve installed an empty Laravel installation and got the following error message when navigating to http://localhost/kanban/public/:

Notice: Use of undefined constant MCRYPT_RIJNDAEL_128 – assumed ‘MCRYPT_RIJNDAEL_128’ in /Library/WebServer/Documents/xxx/config/app.php on line 83

Googling for this error message return many tutorials on how to install mcrypt on Mac OS X (whether building it from source or using Homebrew). The problem was that both the mcrypt and the php55-mcrypt packages were properly installed:

$ brew install mcrypt
Warning: mcrypt-2.6.8 already installed
$ brew install php55-mcrypt
Warning: php55-mcrypt-5.5.20 already installed

Mcrypt was also properly loaded by PHP:

$ php -m | grep mcrypt
mcrypt

$ php -i | grep mcrypt
Additional .ini files parsed => /usr/local/etc/php/5.5/conf.d/ext-mcrypt.ini,
Registered Stream Filters => zlib.*, bzip2.*, convert.iconv.*, string.rot13, string.toupper, string.tolower, string.strip_tags, convert.*, consumed, dechunk, mcrypt.*, mdecrypt.*
mcrypt
mcrypt support => enabled
mcrypt_filter support => enabled
mcrypt.algorithms_dir => no value => no value
mcrypt.modes_dir => no value => no value

Looking at the results of phpinfo() in a Web browser, I could notice two things:

  1. No mention of mcrypt being loaded
  2. Scan this dir for additional .ini files: /Library/Server/Web/Config/php

Actually the directory /Library/Server/Web/Config/php didn’t exist (neither did the parent directories). Since the command line php seemed to scan the directory /usr/local/etc/php/5.5/conf.d for additional .ini files, I did the following:

First, I created the directory PHP wanted to scan:

sudo mkdir -p /Library/Server/Web/Config/php

Then, I created a symbolic link from the crypt ini file loaded by my PHP CLI to this directory:

sudo ln -s /usr/local/etc/php/5.5/conf.d/ext-mcrypt.ini /Library/Server/Web/Config/php/ext-mcrypt.ini

Finally, I restarted Apache:

sudo apachectl restart

After that checking the output of phpinfo() in my browser, I could see the following:

mcrypt

mcrypt support enabled
mcrypt_filter support enabled
Version 2.5.8
Api No 20021217
Supported ciphers cast-128 gost rijndael-128 twofish arcfour cast-256 loki97 rijndael-192 saferplus wake blowfish-compat des rijndael-256 serpent xtea blowfish enigma rc2 tripledes
Supported modes cbc cfb ctr ecb ncfb nofb ofb stream

 

Directive Local Value Master Value
mcrypt.algorithms_dir no value no value
mcrypt.modes_dir no value no value

And the Laravel application was working !

PHP: composer install or update causes a PHP Fatal error

When running composer install or update I was getting a PHP fatal error because PHP was using more than 512MB:

$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
PHP Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 72 bytes) in phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/RuleSetGenerator.php on line 123
PHP Stack trace:
PHP 1. {main}() /usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar:0
PHP 2. require() /usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar:14
PHP 3. Composer\Console\Application->run() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/bin/composer:43
PHP 4. Symfony\Component\Console\Application->run() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Console/Application.php:83
PHP 5. Composer\Console\Application->doRun() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/vendor/symfony/console/Symfony/Component/Console/Application.php:121
PHP 6. Symfony\Component\Console\Application->doRun() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Console/Application.php:117
PHP 7. Symfony\Component\Console\Application->doRunCommand() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/vendor/symfony/console/Symfony/Component/Console/Application.php:191
PHP 8. Symfony\Component\Console\Command\Command->run() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/vendor/symfony/console/Symfony/Component/Console/Application.php:881
PHP 9. Composer\Command\InstallCommand->execute() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/vendor/symfony/console/Symfony/Component/Console/Command/Command.php:241
PHP 10. Composer\Installer->run() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Command/InstallCommand.php:110
PHP 11. Composer\Installer->doInstall() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Installer.php:210
PHP 12. Composer\DependencyResolver\Solver->solve() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Installer.php:449
PHP 13. Composer\DependencyResolver\RuleSetGenerator->getRulesFor() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/Solver.php:166
PHP 14. Composer\DependencyResolver\RuleSetGenerator->addRulesForUpdatePackages() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/RuleSetGenerator.php:275
PHP 15. Composer\DependencyResolver\RuleSetGenerator->addRulesForPackage() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/RuleSetGenerator.php:235
PHP 16. Composer\DependencyResolver\RuleSetGenerator->createConflictRule() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/RuleSetGenerator.php:204

Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 72 bytes) in phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/RuleSetGenerator.php on line 123

Call Stack:
0.0100 385808 1. {main}() /usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar:0
0.0103 387560 2. require(‘phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/bin/composer’) /usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar:14
0.0308 2886688 3. Composer\Console\Application->run() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/bin/composer:43
0.0334 3185464 4. Symfony\Component\Console\Application->run() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Console/Application.php:83
0.0345 3310400 5. Composer\Console\Application->doRun() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/vendor/symfony/console/Symfony/Component/Console/Application.php:121
0.0354 3389704 6. Symfony\Component\Console\Application->doRun() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Console/Application.php:117
0.0355 3390560 7. Symfony\Component\Console\Application->doRunCommand() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/vendor/symfony/console/Symfony/Component/Console/Application.php:191
0.0356 3390896 8. Symfony\Component\Console\Command\Command->run() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/vendor/symfony/console/Symfony/Component/Console/Application.php:881
0.0360 3394928 9. Composer\Command\InstallCommand->execute() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/vendor/symfony/console/Symfony/Component/Console/Command/Command.php:241
0.1072 6425464 10. Composer\Installer->run() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Command/InstallCommand.php:110
0.1081 6525856 11. Composer\Installer->doInstall() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Installer.php:210
3.8979 41690568 12. Composer\DependencyResolver\Solver->solve() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/Installer.php:449
3.8985 41756784 13. Composer\DependencyResolver\RuleSetGenerator->getRulesFor() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/Solver.php:166
3.9001 41901248 14. Composer\DependencyResolver\RuleSetGenerator->addRulesForUpdatePackages() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/RuleSetGenerator.php:275
3.9001 41903408 15. Composer\DependencyResolver\RuleSetGenerator->addRulesForPackage() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/RuleSetGenerator.php:235
104.6757 536552128 16. Composer\DependencyResolver\RuleSetGenerator->createConflictRule() phar:///usr/local/Cellar/composer/1.0.0-alpha8/libexec/composer.phar/src/Composer/DependencyResolver/RuleSetGenerator.php:204

This wasn’t caused by an old version of PHP as I was using PHP 5.5.19:

$ php –version
PHP 5.5.19 (cli) (built: Nov 23 2014 20:46:27)
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.5.0, Copyright (c) 1998-2014 Zend Technologies
with Xdebug v2.2.6, Copyright (c) 2002-2014, by Derick Rethans

So increasing the maximum memory limit in php.ini would probably solve it but more than 512MB just for a composer install seemed a little bit excessive. So since my PHP install had a current version, I checked composer:

$ composer –version
Composer version 1.0.0-alpha8 2014-01-06 18:39:59

OK, it wasn’t that old, but I checked anyway whether there was a newer version and found one:

$ composer selfupdate
Updating to version 029f7093009f621bd20aef98c7bfc61631f18cf1.
Downloading: 100%
Use composer self-update –rollback to return to version 1.0.0-alpha8

After updating to alpha9, I was able to run composer install !! Sometimes fixing your problem is as easy as making sure you use the latest version of all involved software !

It looks like the memory usage of composer was improved in the latest update. But since composer is still using a lot of memory (depending on the packages you have configured), when using it on your development machine, you should set a high memory limit in php.ini and when deploying to your production server it is best to use a composer.lock especially if you’re on shared hosting.

So you should allocate a lot of memory to PHP on your development machine, only run composer update there and only run composer install with a composer.lock file on your deployment machine.

Also, I have tried it but it looks like it helped some people to disable xdebug.

WordPress: Network-wide plugin settings

When developing a plugin, which will be used on single site, as well as multi-site, there are two ways of supporting multi-site deployments:

  1. The plugin is activated per site and provides per-site settings only.
  2. The plugin can be activated for the whole network and provides network-wide settings

I’m currently working on a plugin which needs to support both scenarios. The first scenario is pretty straight forward. But the second one is a little bit trickier.

  • Proper support of the network activation
  • Activation for new sites
  • Network-wide settings
  • Proper support of the network deactivation

Proper support of the network activation

To activate the plugin in a multisite environment, you’ll need go through all the blogs and activate them individually e.g.:

register_activation_hook( __FILE__, 'my_plugin_activate' );

function my_plugin_activate($network_wide)
{
	global $wpdb;

	if (function_exists('is_multisite') && is_multisite() && $network_wide) {
		$current_blog = $wpdb->blogid;
		$blogs = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
		foreach ($blogs as $blog) {
			switch_to_blog($blog);
			my_plugin_activate();
		}
		switch_to_blog($current_blog);
	} else {
		my_plugin_activate();
	}
}

If we are in a multisite environment but the plugin is only activated to for a blog, a normal activation is performed. Otherwise we fetch the list of all blogs and activate them one by one before reverting to the current blog.

Note that you should not use restore current blog() to get back to the original blog since it will revert to the blog active before the last switch_to_blog(). So if switch_to_blog() is called twice it will not revert to the blog which was active before this all started.

Activation for new sites

When a new blog is added, you’ll need to specifically activate the plugin for this blog since this blog was not present, when the activation occurred.

add_action( 'wpmu_new_blog', 'activate_new_blog' );
 
function activate_new_blog($blog_id) {
    global $wpdb;
 
    if (is_plugin_active_for_network('plugin_name/plugin_main_file.php')) {
	switch_to_blog($blog_id);
	my_plugin_activate();
	restore_current_blog();
    }
}

So first we check whether the plugin was activated for the whole network. If it is the case, we switch to the new blog, activate the plugin for this blog and switch back to the previous blog. Here it is fine to use restore_current_blog() since we only use one switch_to_blog().

Network-wide settings

In single site environment, you would use the functions get_option() and update_option() to respectively read and write your plugin settings. In a multi-site environment, you should rather use the get_site_option() and update_site_option() functions instead.

So instead of:

get_option('my_settings', array())
...
update_option( 'my_settings', $settings );

Use:

get_site_option('my_settings', array())
...
update_site_option( 'my_settings', $settings );

When registering your settings page, instead of using options-general.php as parent slug, when calling add_submenu_page, if your plugin has been network activated, you’ll need to add the submenu page using settings.php as parent slug.

Also when linking to your settings page (i.e. from the plugins list), in a single site environment, you’d do it this way:

<a href="options-general.php?page=my_settings">Settings</a>

But to link to the network settings page, you’ll need to use the following

<a href="settings.php?page=my_settings">Settings</a>

The hook for adding the link is also different. Instead of adding a filter for ‘plugin_action_links_pluginname_pluginfile’, you’ll have to add a filter for ‘network_admin_plugin_action_links_pluginname_pluginfile’.

Also the hook to use to call your registering function is different, instead of adding an action to the ‘admin_menu’ hook, you should add an action to the ‘network_admin_menu’ hook in a network activation scenario.

In your settings page, when in a single site environment, you can use the settings API and do not need to update your settings manually, by using options.php as form action:

<form method="post" action="options.php">

For network-wide settings it’s not so easy. There is no direct equivalent to options.php for such settings. You will need to direct to edit.php and provide an action name which will be linked to one of your functions where you will update the settings. Here the registration and definition of this function:

add_action('network_admin_edit_my_settings', __FILE__, 'update_network_setting');

function update_network_setting() {
	update_site_option( 'my_settings', $_POST[ 'my_settings' ] );
	wp_redirect( add_query_arg( array( 'page' => my_settings, 'updated' => 'true' ), network_admin_url( 'settings.php' ) ) );
	exit;
}

Of course instead of my_settings, you should use the name of your settings. The name of the action hook is network_admin_edit_ followed by an action name which will be used as parameter to edit.php in the form definition e.g.:

<form method="post" action="edit.php?action=my_settings">

Of course, since you want to support both the normal and the network activation scenarios, you’ll have to use is_plugin_active_for_network() to figure out in which scenario you are and trigger the appropriate logic.

Proper support of the network deactivation

For the deactivation, you need to do exactly the same you’ve already done for the activation i.e.:

register_deactivation_hook( __FILE__, 'my_plugin_deactivate' );

function my_plugin_deactivate($network_wide)
{
	global $wpdb;

	if (function_exists('is_multisite') && is_multisite() && $network_wide) {
		$current_blog = $wpdb->blogid;
		$blogs = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
		foreach ($blogs as $blog) {
			switch_to_blog($blog);
			my_plugin_deactivate();
		}
		switch_to_blog($current_blog);
	} else {
		my_plugin_deactivate();
	}
}

function my_plugin_deactivate()
{
	//deactivate the plugin for the current blog
}

Of course in a network-wide deactivation scenario, you may want to add some additional cleanup after the plugin has been deactivated for all blogs.

Conclusion

So supporting all three scenarios (single site, multisite with per blog activation and multisite with network activation) is not so difficult. The only tricky part is handling network-wide settings. Unfortunately, the settings API does not help you much here. But once you’ve done it for a plugin, it’s just a matter of copy&paste.

Note that if you do not perform anything special as part of your activation, you do not need to handle the multisite activation since WordPress will activate the plugins appropriately. It is only required if your plugin does some additional magic since WordPress will not call you for each blog.

PHP doesn’t allow constants to be arrays

If you ever wanted to have a constant containing an array in PHP, you’ll soon notice that the following does not work:

define('MY_CONSTANT', array('value1', 'value2'));

The reason as explained in the PHP language reference is that:

Only scalar data (boolean, integer, float and string) can be contained in constants.

There are three options to come close to a constant containing an array:

  1. Using serialization
  2. Declaring a static property
  3. Using a getter

Using serialization

Since constants can contain a string, the obvious solution is to transform this array to a string.

Either using the serialize function when defining the constant and the unserialize function before using it:

// Define the constant as a serialized array

define('MY_CONSTANT', serialize(array('value1', 'value2')));

// And unserialize it before usage

$my_constant = unserialize(MY_CONSTANT);

or encoding the array to a string yourself, using a separator and using the explode function to convert it back to an array:

// Define the constant as a serialized array

define('MY_CONSTANT', 'value1,value2');

// And unserialize it before usage

$my_constant = explode(',', MY_CONSTANT);

Declaring a static property

As an alternative to a class constant containing an array, you could defined a static property containing the array:

private static $MY_VALUES = array('value1', 'value2');

Of course, it’s not really the same as a constant since the value could be changed so it is less secure than a constant and you should consider making it private or protected.

Using a getter

In order to prevent the static property from being changed, you should make it private and use a public getter e.g.:

private static $MY_VALUES = array('value1', 'value2');

public static function values($index){
    return self::$MY_VALUES[$index];
}

Of course with an associative array, it makes more sense.

You can also use a getter together with the serialization solution so that you do not need to unserialize explicitely every time the array is used:

private const $MY_VALUES = serialize(array('value1', 'value2'));

public static function values(){
    return unserialize(self::$MY_VALUES);
}

PHP: Errors installing composer in XAMPP on Windows

I have a XAMPP installation on a Windows machine and wanted to install Composer. After downloading it from getcomposer.org I started the installer. After the step where you have to point it to php.exe, I got many error messages along the lines:

Unable to load dynamic library '\software\xampp-portable\php\ext\php_bz2.dll'

I probably got around 20 error popups. The first thing I did was to check whether the library was in there. And it was. Only then did I notice that the path shown didn’t include the drive name. Since I had just shown the installer where php was installed, I thought that it was probably reading the path from the associated php.ini and maybe the path wasn’t complete there. And I was right:

; Directory in which the loadable extensions (modules) reside.
; http://php.net/extension-dir
; extension_dir = "./"
; On windows:
extension_dir = "\software\xampp-portable\php\ext"

After modifying it to:

extension_dir = "d:\software\xampp-portable\php\ext"

And the errors were gone !

PHP: Cannot modify header information – headers already sent

If you’ve ever modified header information in PHP, you’ve probably already seen this error. The first time you see it, it is often difficult to understand what the error means and what could cause such an error.

What ?

First, let’s have a look at what the error means. Whenever your PHP script returns some HTML code, first a header is sent which contains some metadata about the server, caching behavior, cookies, the following content… This header can also be used to redirect the user to a different URL.

You can check the header returned by a URL using this tool.

Once the header has been sent to the client, you cannot modify it anymore. Additionally, once you’ve started sending some contents, the header will have been implicitly sent. So after sending the header whether implicitly or explicitly, you should not try to modify the header anymore.

Modifying the headers

Second let’s see how you can modify the headers using PHP. There are three main functions which modify the headers:

  • header()
  • setcookie()
  • session_start()

So you should not output ANY content before calling any of these functions.

Content Output

Third let’s see how you could ouput some content either knowingly or by mistake.

echo

The obvious way to output some content is using the echo function. So the following will lead to this error:

<?php
echo "some contents"
header('xxx: yyy');
?>

This one is pretty easy to identify. There are two solutions:

  1. Get rid of the echo before the call to the header function
  2. Use ob_start() and ob_flush()

Characters before before <?php

Everything which is not between <? and ?> will be considered to be content. So if you have anything before <?php header('xxx: yyy'); ?>

Or a newline:

<?php
header('xxx: yyy');
?>

Or a Byte-Order-Mark (aka BOM). For more information regarding the BOM and how to remove it, please check this article.

The solution in all these cases is to get rid of everything before <?php.

HTML before PHP code

Similar to the previous issue, you could also have HTML code on purpose before your PHP code e.g.:

<html>
<body
<?php
...
header('Location: http://...');
...
?>
</body>
</html>

If you mix HTML and PHP, you should never call header, setcookie or session_start in the php code found after some HTML code. If you need to call any of these functions, you should use ob_start() and ob_flush() and output the whole HTML content from you PHP code.

Characters after before ?>

Similar to the problem with characters before before <?php, if you include a PHP script in another and the included PHP script contains some characters after ?>, you will be in trouble. This is because those characters will be considered content and output before the rest of the code in the including PHP file.

The solution is again the same: remove all empty lines and white spaces after ?>.

Buffered output

One solution mentioned above is to use ob_start() and ob_flush() to buffer output. This means that all content output after the call to ob_start() will be buffered and only sent when you call ob_flush().

Here’s a short example:

<?php 
ob_start(); 
?>
<h1>My heading</h1>
<?php
session_start();
ob_flush();
...
?>

In this example, the H1 tag will not be output until ob_flush() is called, so after the call to session_start().

There is also another function called ob_end_flush(). The difference is that ob_flush flushes the contents of the buffer but keeps the buffer alive, so content after ob_flush() will also be buffered. When you call ob_end_flush(), you flush the buffer and destroy it (i.e. turn off output buffering). So you’d use ob_flush() to already send some content to the client, while further producing additional content. And you use ob_end_flush() once the whole content has been added to the buffer.

PHP and MySQL: SQL injection

First let’s have a look at what SQL injection is about. SQL injection means that an attacker is injecting some pieces of SQL code in a call to a server instead of just sending some text information in order to go around security mechanisms or in order to perform something which shouldn’t be allowed.

Here’s a very simple example. Let’s say you have a very poorly programmed login function which is called with two parameter, a user name and a password. If you take the parameters and built an SQL statement like this:

$query = "SELECT 1 FROM users WHERE user_id='".$user_name."' AND password='".$password"'"

An attacker may send the following:

  • User name: admin' —
  • Password: anything

The generated SQL query would be:

SELECT 1 FROM users WHERE user_id='admin' --' AND password='anything'

The double dash would make the rest of the line a comment and the statement would always return 1 allowing the attacker to login as admin without valid credentials.

An easy fix for this security issue is not to return 1 but to return an MD5 of the password and compare it with the password provided. Unfortunately, it is also trivial to workaround such security fixes. Let’s say you statement now looks like this:

SELECT password FROM user WHERE user_name='xxx'

All the attacker has to do is to use the following username: admin' AND 1=0 UNION SELECT 'known_md5_checksum

But SQL injection is not only used to be able to login without credential. It can be used to perform actions which are not intended to be allowed. This is typically done by using batched queries i.e. closing the first query and having a second query executed which would either return sensitive information or destroying something e.g. using a user name like: admin'; DROP TABLE important_table —

The first statement will be executed normally and the drop table will then be executed additionally. Fortunately, when using PHP and MySQL these kind of batched queries are not supported. The execution will fail since you can only have one statement executed at a time. But it you used PostgreSQL instead of MySQL it would be possible.

Another thing which is often done using SQL injection is getting access to data in the database which should be protected. This is usually done using a kind of UNION injection. The idea behind it is that:

  • the server you are attacking is fetching data using a query and displaying this data in a tabular form
  • you inject a UNION clause to fetch data from another table
  • the data from both table are displayed in the table on the client

Let’s say you have an order table and you can call the server to display all items in a particular order with such a statement:

$statement = "SELECT name, value FROM items WHERE order_id=".$order_id;

If the attacker now sends the following as order_id, he will get a list of all users and their passwords: 1 UNION SELECT name, password FROM users

I do hope the passwords will be at least encrypted but encryption alone is not always enough to protect data. You also need to make sure the encrypted data cannot be accessed that easily.

Of course an attacker will need to know which kind of statements are executed by your software in order to exploit such a security hole. But it might not be as difficult as you think… I’ll post another article about how you can manage to get information about the query being executed later.

Many database engines also provide the functionality to execute commands in the operating system from SQL. The commands are executed using the user running the database engine. It’s sometimes very useful but in an SQL injection scenario it may allow an attacker to not only steal information or damage the database but also the operating system. Fortunately, in our case the xp_cmdshell command used to do this (MSSQL Server and Sybase) does not exist in MySQL.

So what is to be done to protect yourself against SQL injection attacks ?

First if you use MSSQL Server or Sybase: to prevent an attacker from destroying the whole server or prevent him from getting access to any file on the computer, you should disable xp_cmdshell or run the database engine with a user with very limited access rights to the rest of the system.

Now, let’s get back to MySQL and PHP. Here you should use Use prepared statements and parameterized queries using PDO or Mysqli. These statements are sent to the database engine and are parsed independently of anything else. Like this it is not possible to inject SQL code in a parameter.

An example using Mysqli:

$stmt = $db->prepare('SELECT password FROM users WHERE user_name = ?');
$stmt->bind_param('s', $user_name);
$stmt->execute();
$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    // compare the returned value with the MD5 sum of the provided password
}

And using PDO:

$stmt = $pdo->prepare('SELECT password FROM users WHERE user_name = :user_name');
$stmt->execute(array('user_name' => $user_name));
foreach ($stmt as $row) {
    // compare the returned value with the MD5 sum of the provided password
}

Note that PDO is an extension but it is bundled by default since PHP 5.1 and a MySQL driver is also available by default.

This is basically the best way to secure your software. In case you cannot use PDO or Mysqli, there are other techniques to prevent SQL injection attacks. I’ll list them in an update to this post in a few days.

Mac: PHP Startup: Unable to load dynamic library gettext

I’ve started today a new project in PHP on my Mac and started with the following error message in my php error log file:

[19-Jan-2014 10:39:14 UTC] PHP Warning: PHP Startup: Unable to load dynamic library '/usr/lib/php/extensions/no-debug-non-zts-20100525/php_gettext.dll' – dlopen(/usr/lib/php/extensions/no-debug-non-zts-20100525/php_gettext.dll, 9): image not found in Unknown on line 0

So first you’ll notice it’s looking for a DLL on my Mac so obviously it won’t work. The first thing to check is php.ini:

$ grep extension= /etc/php.ini | grep -v "^;"
extension=php_gettext.dll

Ok so it’s really writte .dll in there. I’m not too sure whether I did it some time ago or whether it was installed like this. I’m pretty sure it used to work fine in the past since I was working on another project which didn’t have any problem with gettext. Inbetween I’ve upgraded to Mavericks but it might just be that I was working very late at night and modified php.ini on my Mac instead of doing it in my Windows virtual machine.

So I changed .dll to .so but without better results. Of course, I had checked the referenced extensions path first, I’d have seen that there are no files there except for xdebug.so.

So it looks like I’ll have to install php_gettext first. That’s not sp difficult.

First you need to find out which version of PHP is installed so that you can download the sources for this version:

$ php --version | head -1 | awk ' { print $2; } '
5.4.17

If you have a different version, you’ll need to change 5.4.17 to that version in all commands below.

Then create a working directory for building gettext for PHP:

mkdir /tmp/gettext
cd /tmp/gettext

Now we need to download the PHP sources and uncompress them:

curl --location --progress-bar http://museum.php.net/php5/php-5.4.17.tar.gz | tar -zx

Then we’ll install the latest version of gettext using Homebrew:

brew update
brew install gettext

Then we’ll prepare the PHP extension for compiling:

$ cd /tmp/gettext/php-5.4.17/ext/gettext/
$ phpize
grep: /usr/include/php/main/php.h: No such file or directory
grep: /usr/include/php/Zend/zend_modules.h: No such file or directory
grep: /usr/include/php/Zend/zend_extensions.h: No such file or directory
Configuring for:
PHP Api Version:        
Zend Module Api No:     
Zend Extension Api No:  

So something is missing. After a long search it seems I needed to install the command line developer tools:

$ xcode-select --install
xcode-select: note: install requested for command line developer tools

$ phpize
Configuring for:
PHP Api Version:         20100412
Zend Module Api No:      20100525
Zend Extension Api No:   220100525

Now it looks better so we can continue, with configuring and building gettext (use the version number installed by brew instead of 0.18.3.2 in the command below):

$ ./configure --with-gettext=/usr/local/Cellar/gettext/0.18.3.2
...
$ make
...
----------------------------------------------------------------------
Libraries have been installed in:
   /tmp/gettext/php-5.4.17/ext/gettext/modules

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the `DYLD_LIBRARY_PATH' environment variable
     during execution

See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.
----------------------------------------------------------------------

Build complete.
Don't forget to run 'make test'.

So the shared library /tmp/gettext/php-5.4.17/ext/gettext/modules/gettext.so was created and we just need to copy it as php_gettext.so with:

$ sudo cp /tmp/gettext/php-5.4.17/ext/gettext/modules/gettext.so /usr/lib/php/extensions/no-debug-non-zts-20100525/php_gettext.so

And restart apache:

$ sudo /usr/sbin/apachectl restart

And now the error is gone ! You can now safely remove the directory /tmp/gettext.

PHP: Cannot connect to MySQL on localhost

I decided some time ago to move on my Mac from MAMP to a native installation of Apache, MySQL and PHP. After installing everything I startet my application and got an error saying the database connection failed. I checked that MySQL was running and that I could connect to it manually. Everything was fine. Running under MAMP with the exact same configuration everything worked. The final deployment server is a Debian machine and there it worked with the exact same settings too. It also worked on a Windows machine using XAMPP.

It was getting late and I just couldn’t understand what the problem was, I read each character of the configured username, password and database name like 100 times to make sure I had everything right. Then I just changed the host from localhost to 127.0.0.1 and didn’t expect anything to change but there it was, my application could connect to the database !

On that evening I just went to bed making a mental note I had to understand what was the difference. The next day I did some research and could figure out what was the problem:

Just looking at the mysql_connect page in the PHP online manual brought the answer. There is a note saying:

Whenever you specify “localhost” or “localhost:port” as server, the MySQL client library will override this and try to connect to a local socket (named pipe on Windows). If you want to use TCP/IP, use “127.0.0.1” instead of “localhost”.

Of course, when I searched for an answer online before I found the solution I never saw this manual page. But now it’s clear.

It’s rather confusing since it effectively means that MySQL has redefined localhost to mean “connect to a unix domain socket”. And when it uses unix domain socket, it will ignore whatever you define as a port. This is of course kind of an issue if you want to have multiple instances of MySQL running.

It also looks like the default behavior on Windows is to use TCP-IP. But on Unix-like operating systems, it depends on whether you use localhost or 127.0.0.1.

If you need to use localhost and cannot configure 127.0.0.1, you’ll have to use socat to establish a relay between a unix domain socket and the MySQL tcp port.

One of the reason why it works with some localhost on a machine and not on the other might also be that the path to the unix domain socket is not the one you expect. Usually the path would be /tmp/mysql.sock. But if your mysql instance uses a different one, you should adapt the mysql.default_socket setting in php.ini and point it to the right path (e.g. /opt/local/var/run/mysql5/mysqld.sock, /var/mysql/mysql.sock, /private/tmp/mysql.sock or /usr/local/mysql/run/mysql_socket). If you’re using PDO, the setting you need to change is probably pdo_mysql.default_socket. You should be able to find the right path using the following:

mysqladmin variables | grep socket

or this:

mysqld --verbose --help | grep "^socket"

or this:

mysql_config.pl --socket

You can read the location where PHP looks for the MySQL socket in php.ini (mysql.default_socket, mysqli.default_socket and pdo_mysql.default_socket). If you have no php.ini file yet copy php.ini.default or rename it:

sudo cp /etc/php.ini.default /etc/php.ini

You can then change the path there. After changing the path, you need to restart the Apache web server e.g.:

sudo apachectl restart

If you do not want to change php.ini, you can also create a link:

mkdir /var/mysql
ln -s /tmp/mysql.sock /var/mysql/mysql.sock

You might have to use sudo to have the permissions to perform the above actions. Also make sure that the permission of the /var/mysql directory are appropriate.

Note that if you see the socket at the right location but disappearing for unknown reasons, you my want to trying deleting the /etc/my.cnf global options file. I saw this somewhere during my search on the internet but do not quite remember where.

On the other hand instead of changing the path in php.ini you may want to change the path in the MySQL configuration (my.cnf):

[mysqld]
socket=/var/mysql/mysql.sock

And restart the MySQL server.

Note that using the following hostname for the connection should also work although I haven’t tried it myself:

localhost:/tmp/mysql.sock

(change /tmp/mysql.sock to the path to the actual MySQL socket).

If you are on Mac OS X, please read the General Notes on Installing MySQL on Mac OS X. It states that the default location for the MySQL Unix socket is different on Mac OS X and Mac OS X Server depending on the installation type you chose and provides a table with a list of location depending on the installation type.

In MAMP, the location should be /Applications/MAMP/tmp/mysql/mysql.sock. So if you are using an external PHP installation with MySQL from MAMP, you’ll probably need to run the following:

sudo ln -s /Applications/MAMP/tmp/mysql/mysql.sock /tmp/mysql.sock