I really need to think of a clever name for my blog

Code samples, geeky links and other musings. Note this blog has recently moved from Posterous, so isn't fully back up to speed yet!

New features in PHP 5.5

Either this is a good update, or I’m a better coder than I used to be – all of the changes mentioned here are very tasty, rather than making me go ‘whuuuuuh?’…

http://net.tutsplus.com/tutorials/php/what-to-expect-from-php-5-5/

Cleaning up PHP – Terminal conditions with continue;/return;

PHP (or any language where code blocks are indented) can get a bit messy sometimes

for($i = 0; $i <= $n; $i++){    if ($some_conditon) {        if ($some_conditon2) {            if ($some_conditon3) {                if ($some_conditon4) {                    if ($some_conditon5) {                        do_something();                    }                }            }        }    }}

OK, that’s not a literal example, but it does happen. If your condition is terminal, that is: if it is not met, no further action is taken; then there is no need to wrap a massive chunk of code in curly braces and indent it yet further. Break out of the cycle! If you are in a loop, you can just continue; – for example instead of:

for($i = 0; $i <= $n; $i++){    if($terminal_condition != false){        do_something();    }}

you can do this:

for($i = 0; $i <= $n; $i++){    if($terminal_condition == true){        continue;    }    do_something();}

In this ridiculously abbreviated example, it does look longer, and yes it will always result in more code, but where you have several terminal conditions in a section of logic which are separated by a number of lines, this technique can save you one hell of a lot of indenting, making your code that much more readable. Our original example, while longer, would be a lot simpler to follow:

for($i = 0; $i <= $n; $i++){    if (!$some_conditon) {        continue;    }    if (!$some_conditon2) {        continue;    }    if (!$some_conditon3) {        continue;    }    if (!$some_conditon4) {        continue;    }    if (!$some_conditon5) {        continue;    }    do_something(); }

In a similar way to loops, if you are in a function, you can simply return; (or return true/false; as appropriate) earlier rather than doing it in an else statement; Shocking? No. A revelation? Hardly, these are not new constructs. But common? Not from the code I see. Give it a go. I will…

Social Engine 4: Unable to delete user [Solution]

I am new to Social Engine, but have already found a number of quite surprising issues with it. As such my analysis relies upon a certain amount of logic and assumption. Ironic really, as assumptions are what cause this particular issue… 

Symptoms:

A user trying to delete their profile from http://{domain.com}/members/settings/general gets an 500 Internal Server Error.

Administrators attempting to delete profiles from admin get delete confirmation modal popup. On clicking delete, the popup turns white and user is not deleted. Inspecting traffic in the Firebug Net panel, the ajax request is returning a 500 Internal Server Error.

Checking {domain}/statistics/logs/error_log reveals the following errors generated each time:

PHP Fatal error:  Call to a member function remove() on a non-object in /path/to/domain/application/modules/Album/Model/Photo.php on line 150PHP Fatal error:  Undefined class constant 'PRIMARY_TYPE_NUM' in /path/to/domain/application/modules/Core/Model/DbTable/Session.php on line 538

 

Cause:

I believe the cause is a user with missing images that are listed in the database. This in itself should not be an issue, but it seems the code makes fatal (!) assumptions regarding the existance of images that are listed in the database, a rookie mistake if ever there was one. When dealing with files on the file system, one must always confirm their existance, and if possible validate the file content before attempting to operate on it, else you are open to software errors and worse, security risks.

In this case the files are dealt with through objects, representing the file and the possible methods, or operations, upon it. Where a file does not exist, the object is not created, so it is the object, rather than the file whose existance needs confirmation before operating upon it.

As the source code itself notes:

    // This is dangerous, what if something throws an exception in postDelete
    // after the files are deleted?

While the author of this note is considering a different edge case, this would seem to be a worrying indicator of the standard of coding .

The ‘Undefined class constant’ error is merely a consequence of the prior fatal error.

The offending lines are

application/modules/Album/Model/Photo.php 
l.149 – 150
      $file = $this->api()->getApi('storage', 'storage')->get($this->file_id, null);      $file->remove();

You could say that it is 149 that is the cause, rather than 150. 150 tries to do a perfectly valid operation upon the presumed object, but it is 149 that has failed to return an object as expected.

By that token however the cause lies in the storage API, in that it fails to return an object in the first place.

Note that exactly the same careless approach is taken immediately after this operation for thumbnails, and then again (although commented out) for cropped versions.

 

Solution:

Hack:

Add an if(is_object($object)) check before the operation:

Unix Diff:

application/modules/Album/Model/Photo.php 
150c150,152 <       $file->remove(); --- >       if(is_object($file)){ >         $file->remove(); >       } 152c154,156 <       $file->remove(); --- >       if(is_object($file)){ >         $file->remove(); >       }

For clarity, we are replacing:

l.149-152:
      $file = $this->api()->getApi('storage', 'storage')->get($this->file_id, null);       $file->remove();       $file = $this->api()->getApi('storage', 'storage')->get($this->file_id, 'thumb.normal');       $file->remove();

with

l.149-156:
      $file = $this->api()->getApi('storage', 'storage')->get($this->file_id, null);       if(is_object($file)){         $file->remove();       }       $file = $this->api()->getApi('storage', 'storage')->get($this->file_id, 'thumb.normal');       if(is_object($file)){         $file->remove();       }

I am unsure as to just what the failed operation on 149 had returned – if it was anything equating to FALSE, then an alternative and more succinct solution might be:

      if($file = $this->api()->getApi('storage', 'storage')->get($this->file_id, null)){         $file->remove();       }

As this is little more than a dirty hack, I haven’t taken the time to look into this further. I don’t want to be creating users and deleting them just to test out a hunch. 

Proposed system solution:

The storage API should still return an object when a file is not found. This object should contain a ‘not found’ flag, and should not contain a remove() method, or the remove() method of said object could simply log the non existance of the file when the ‘not found’ flag is set.

The object could also reference a ‘not found’ image for use in the front end of the site in the case of images.

The advantage of this approach is that only the object definitions would need to be changed.

Alternatively, every instance of a file operation needs to have a check coded into it as per the hack above.

Further remedial action:

Having corrected this error, the system still failed to delete the user, this time throwing the following error

PHP Fatal error:  Call to a member function getNextCollectible() on a non-object in /path/to/domain/application/modules/Core/Model/Item/Collectible.php on line 54PHP Fatal error:  Undefined class constant 'PRIMARY_TYPE_NUM' in /path/to/domain/application/modules/Core/Model/DbTable/Session.php on line 538

While I am not familiar enough with Social Engine to know exactly what collectible items are (Humbold figurines? Pokémon? I dunno…), it is the same error and can be fixed with a similar approach:

application/modules/Core/Model/Item/Collectible.php
54c54,56<     return $this->getCollection()->getNextCollectible($this); --- >     if(is_object($this->getCollection())){ >       return $this->getCollection()->getNextCollectible($this); >     }

At this point the user could finally be deleted. 

PHP File Upload Progress Bar

A pure PHP based (I.E. not Flash or Java based) method of monitoring file upload progress used to be something of a holy grail for web designers for some time, especially for people creating CMS or otherwise handling user provided content. I remember seeing mention of it being on the horizon many, many moons ago, but hadn’t looked into it for a long time. Implementations in the CMS I have used is still very rare – where there is any it is still often Flash.

Installing APC recently, I noted the apc.rfc1867 configuration directive which states:

RFC1867 File Upload Progress hook handler is only available if APC was compiled against PHP 5.2.0 or later.

Aha, so that’s the key is it? Time to investigate again methinks.

I shall at some point be taking a look at

http://www.johnboy.com/blog/a-useful-php-file-upload-progress-meter

and

http://www.johnboy.com/php-upload-progress-bar/

The second is his more recent implementation. At first glance it appears to use an iFrame – well, this is the ajax age, so I hardly think that should be an issue to bring bang up to date.

I shalll be returning to this!

Integrating the Zencoder API

I have been working on integrating the Zencoder API with my CMS.

Where you have a configuration file that users can, of course, completely mangle, you need to do a lot of error checking to ensure that things are correctly set and keep the users informed.

Most of the things you will wanting to be checking are pretty straightforward, but one of the slightly more complex ones is the protocol for returning the encoded files.
Zencoder supports ftp, sftp and ftps, as well as Cloud File and Amazon S3.
Regex for testing protocols is widely available online, but we are looking at slightly less usual options than http(s) vs ftp.

Protocol:

First we need to allow for all 3 flavours of ftp
/^(s?(ftp)s?):///

Cloud file uses their cf:// protocol, or alternatively cf+xx:// where xx is the two letter country code of the location – currently Cloud File supports us and uk, defaulting to us if not specified. However, realistically, Cloud File may support more countries in future, so we need to allow for them.
/^((cf)(+[a-z]{2})?):/// 

Amzon S3 uses their s3:// protocol.
/^(s3):///i';

Sticking all three together we get:
/^(s?(ftp)s?|((cf)(+[a-z]{2})?)|s3):///'

To put it into action:
$host = 'ftps://mydomain.com';$zencoder_protocol_regex = '/^(s?(ftp)s?|((cf)(+[a-z]{2})?)|s3):///i';if(strpos($host, '://') > 0){ preg_match($zencoder_protocol_regex, $host, $result); if(count($result) == 0){ //Incorrect protocol }else{ if($result[0] = 'ftp://'){ //Protocol is ftp - give security warning } $protocol = $result[0]; $host = str_replace($result[0], '' , $host); }}else{ $protocol = 'ftps://';}

URL with username and password:

The next crucial step is to construct a valid destination url that includes a username and password, while allowing for S3 accounts, which don’t require user/pass. Non alpha-numeric characters need to be percent encoded:
$user = 'username';$pass = 'password';$file = 'filename.ext';if(strlen($user) > 0){ $user = rawurlencode($user) . ':';}if(strlen($pass) > 0){ $pass = rawurlencode($pass) . '@';}$output = $protocol . $user . $pass . $host .  '/' . $file

So if we pass in the following variables:
$host = 'ftps://ftp.mydomain.com';$user = 'bob@mydomain.com';$pass = 'foo!';$file = 'filename.ext';

We will get:
ftps://bob%40mydomain.com:foo%21@ftp.mydomain.com

Some notes on FTP protocols:

FTP: Completely unsecure – username and password sent in plain text
FTPS: User/pass sent over TLS/SSL – data not encrypted
SFTP: All data encrypted over SSH
So why not use SFTP all the time? Well, sometimes it’s just not available if you are on a shared server, and if it is, it’s often not available for additional FTP users as SSH requires shell access. As you don’t want to be giving away your master FTP account details, an additional FTP account (which can point directly at the required folder) is preferable, and unless your videos are highly sensitive or valuable, FTPS should do just fine.

Want to see it all put together?

http://pastebin.com/iB3X1Dq5
This is the production code on my server, so it has a lot of things that are custom to the CMS – it should be pretty straightforward to follow though. You will also need to trawl it for the variables that need to be passed to the script, but you should be reading the Zencoder docs to familiarise yourself with the requirements anway. Sorry I don’t have time to expand it all currently.
It is configured to output webm, ogg and mp4 files for HTML5 video players.

You will also need the API class from Zencoder – here’s a copy for convenience:
http://pastebin.com/eVbF879j
I haven’t yet integrated thumbnails, or processing a Zencoder notification returned to a script 

AES-256 decryption with PHP & Mcrypt

I’ve recently had to decrypt some data from a third party API, encrypted with AES-256.

Thanks to an example implementation only in C# and a quirk of PHP, I stumbled for quite a while.

AES-256 is a derivation of the Rijndael cipher, but the two are not quite the same. Mcrypt only lists MCRYPT_RIJNDAEL_128, MCRYPT_RIJNDAEL_192 and MCRYPT_RIJNDAEL_256 – no AES.

I did try checking out PHP’s OpenSSL ( >=PHP 5.3), where an example on the page lists aes-256-cbc as a supported standard, but despite the documentation and examples listing up to 5 arguments, the last being the crucial (in this implementation) Initialisation Vector, PHP was throwing an error, claiming OpenSSL could take a maximum of 4 arguments, leaving me to have the final argument with the internet and looking like a nutter in the office.

Some feedback from the API developers firmed up what I was dealing with and spurred me to another bout of research

Cipher settings provided by the API Developers

Critically this confirmed I should be using MCRYPT_MODE_CBC

Finally I found this post: AES-256 using PHP-mcrypt which cleared up the crucial issue – AES-256 support IS possible with mcrypt – by using MCRYPT_RIJNDAEL_128.

Oh of course, how obvious. Silly me…

So, here is a very simple implementation of AES-256 as a CodeIgniter class, which outputs the decrypted data to screen:
http://pastebin.com/szPxdDCw

‘Deprecated’ error warnings – PHP 5.3

Just reviewing some sites in my portfolio, and I notice that a CubeCart installation on a shared host is broken. Oh dear…

Deprecated: Function set_magic_quotes_runtime() is deprecated in /path/to/domain/shop/ini.inc.php on line 114Warning: ini_set(): Cannot change zlib.output_compression - headers already sent in /path/to/domain/shop/ini.inc.php on line 118 Warning: Cannot modify header information - headers already sent by (output started at /path/to/domain/shop/ini.inc.php:114) in /path/to/domain/shop/index_enc_ion.php on line 31 Warning: Cannot modify header information - headers already sent by (output started at /path/to/domain/shop/ini.inc.php:114) in /path/to/domain/shop/index_enc_ion.php on line 32Deprecated: Function eregi() is deprecated in /path/to/domain/shop/includes/functions.inc.php on line 408

Ah, great. Without warning, we’ve been upgraded to PHP 5.3

Thanks for that…

Anyway, it’s easily solved.

With 5.3 a new error level has been introduced – E_DEPRECATED, so it’s easy to suppress deprecated errors:

In your scripts:
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);

In the case of CubeCart 4 this can be acheived in ini.inc.php line 101.

In php.ini:

error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED

in .htaccess:

php_value error_reporting 1

.htaccess requires the correct integer value equivalent of your chosen reporting level – these are a touch obscure, but setting it to 1 means report fatal run-time errors and unrecoverable errors, which will do the trick.

cgi_wrapper: Per-domain php.ini under php-fastcgi (Plesk 9.5.2 + CentOS 5)

I recently discussed per-directory php directives utilising htscanner, allowing setting values with php_flag and php_value in .htaccess files.

While this is an enormous step forward in term of granular control for the webmaster, it doesn’t allow complete control as some things don’t work when set in .htaccess as I discovered when trying to set apc config values.

The problem stems from being unable to use vhosts.conf to specify php_admin_value as you would running PHP as an Apache module, and having to resort to other methods. I did attempt http://kb.parallels.com/9059, but it was a complete failure, because Paralells neglect to mention that you must be running Plesk 9.5.2+ in order to utilise cgi_wrapper as directed.

I had been avoiding updating Plesk, as last time I’d done so it had mangled quite a few things, but I bit the bullet, and this time the process seems to have been entirely successful.

From http://kb.parallels.com/9059

The hotfix is as follows:

It is possible to use custom php.ini for domains if php as FastCGI mode is used on it. 
It is necessary to apply hotfix. Fix is compatible with Parallels Plesk Panel versions 9.x.

Use the following instruction for applying hotfix.

1. Download hotfix from the attachment

# wget http://kb.parallels.com/Attachments/13985/Attachments/cgi_wrapper

2. Locate file cgi_wrapper

# cat /etc/psa/psa.conf |grep CGI_PHP CGI_PHP_BIN /var/www/cgi-bin/cgi_wrapper/cgi_wrapper

3. Create a backup of this file

# cp /var/www/cgi-bin/cgi_wrapper/cgi_wrapper /var/www/cgi-bin/cgi_wrapper/cgi_wrapper_orig

4. Replace cgi_wrapper with the downloaded one

# cp cgi_wrapper /var/www/cgi-bin/cgi_wrapper/cgi_wrapper

5. Copy php.ini to the configuration directory of domain

# cp php.ini /var/www/vhosts/domain.tld/conf/

6. Change permissions on the configuration directory of the domain

# chmod 0755 /var/www/vhosts/domain.tld/conf

7. Restart apache

# /etc/init.d/httpd restart
Should the download become unavailable, I have included the contents of the updated cgi_wrapper here for convenience:
#!/bin/shdomain=`fgrep -m 1 "$UID" /etc/passwd| awk -F: '{print $6}' |awk -F"/" '{print $5}'`PHPRC=/var/www/vhosts/$domain/conf/php.ini[ -f ${PHPRC} ] || PHPRC="/etc/php.ini"export PHPRCexec /usr/bin/php-cgi -c "$PHPRC"

htscanner: Enabling php_admin directives in .htaccess under php-fastcgi (Plesk 9.x + CentOS 5)

[update 201107061858]
For per-domain php.ini under php-fastcgi see here
[/update]

Under Plesk running php-fastcgi  it is not possible to use php_admin_flag and php_admin_value directives in .htaccess files on a per-directory basis. Luckily this can be easily enabled with the htscanner extension.

All the information to get this working is out there, but as per usual, you have to piece it together from a variety of sources to get a complete picture. Here I shall provide a single complete set of instructions that have been proven to work on a CentOS5 box with Plesk 9 – although Plesk should be an irrelevance here, I mention it for completeness.

I am using PuTTY as my SSH client.

In order to install and use htscanner v1.0,0 , you must have the following things (aka dependancies):

PHP Version: PHP 5.1.0 or newer
PEAR Package: PEAR Installer 1.4.8 or newer

Odds of having >=PHP 5.1 without PEAR are slim to my knowledge, and installing it is beyond the scope of this post.

Got those? Ready to go!

To provide absolute clarity for inexperience bods, # is the SSH command prompt and shouldn’t be included in typed commands! And if you REALLY need your hand held, hit [enter] after each line. Just so you don’t sit there wondering why nothing is happening…

Download and extract htscanner

  1. SSH to your server with root access.
  2. Download the latest version of htaccess from http://pecl.php.net/package/htscanner currently 1.0.0
    #  wget http://pecl.php.net/get/htscanner-1.0.0.tgz
  3. Untar the file:
    # tar -xvsf htscanner-1.0.0.tgz

Compile and run installation

  1. Change to the newly created directory:
    # cd htscanner-1.0.0
  2. Run phpize:
    # phpize
  3. Run the configuration script with –enable-htscanner switch
    # ./configure --enable-htscanner
  4. Run make to build the installer:
    # make
  5. Run make install to actually install the extension
    # make install

Add htscanner settings

Either:

For systems that support it, you can simply copy htscanner.ini to your php conf.d directory. This is probably somwhere along the lines of /etc/php.d on CentOS 5 or perhaps for others /etc/httpd/conf.d but if you are having trouble finding it, run
# locate conf.d
You will find a bunch of them though, so make sure it’s the right one. You can also try simply googling your *nix distribution name and conf.d – for example “centos 5 conf.d”

Or:

If you are running SuSE or another flavour of Linux which doesn’t support this, you can add the contents of htscanner.ini to your php.ini file. To locate yours try the tips above for finding the php conf.d directory
The htscanner.ini file currently looks like this:
[htscanner]extension="htscanner.so"; The configuration file htscanner needs to scan for php_* directivesconfig_file=".htaccess"; The fallback docroot when htscanner can't determine the current docrootdefault_docroot="/"default_ttl=300; Stop when an error occured in RINIT (no document root, cannot get path_translated,...)stop_on_error = 0; Warn when an option cannot be setverbose = 0

You should check the contents of your htscanner.ini file though, to be safe.
This is simple enough, but for the sake of completeness and for any poor n00bs out there, to do this, and then add to php.ini using ‘vi’ text editor: 

  1. Open htscanner.ini:
    # vi docs/htscanner.ini
  2. Highlight all text to copy to the clipboard
  3. Exit vi by simply typing
    :q
    and hitting [Enter]
  4. Open php.ini:
    # vi /etc/php.ini
  5. Scroll to the bottom of the file with [pgdn] key
  6. Enter editing mode by hitting [i] key
  7. Press [->] Left arrow key to ensure you are at the end of the last line
  8. Hit [Enter] to start a new line
  9. Right-click the mouse to paste the htscanner.ini contents
  10. Exit editing mode by hitting [Esc] key
  11. Save and quit vi:
    :wq 
    and hit [Enter] 

Restart Apache

On CentOS 5 and similar distros:
#  service httpd restart
For other *nix versions, y’know, google it. Why not, sounds like fun to me?

And voila. You should now be up and running with per directory php_admin directives in your .htaccess files.

Pixie – user editable blocks

Pixie is a simple, lightweight CMS. I had to knock something up double quick, and Pixie did the job nicely. It’s pretty light on modules and plugins, but ripe for customisation as being so lightweight it’s very easy to get into quickly.

In Pixie ‘blocks’ are small sidebar-type elements for additional comments. These are easy enough to put in for a coder, but the developers admit that easily editable block content for users is on their list to do.

Well that just won’t do will it? I need to get some user editable content in there today!

So here’s how to do it.

Create a new page in admin:

Settings > Create a new pagePage Type: Static[Create Page]

Give it a slug and Display NameDon’t give it any blocks

Set:Public: Yes

If you don’t want this content to be accessible from the menu:In Navigation: No

[Save]

Edit the content of the new page:

Publish > {your new page title}Edit page and [Update]

That’s our content ready to go. You can obviously include as much or as little as you like, but as blocks are generally sidebars it’s probably best to keep it simple and minimal.

Next we need to create a new block.This is a REALLY simple file – we leverage the Pixie static page system and hack it into a block file called admin/blocks/block_{myblock}.phpThe {myblock} name can be whatever you want – just make sure it doesn’t clash with any existing blocks. This will be the block name you add to the page you want it to appear in.

And here’s the code for your block. Simply edit $block_static to the value of the page’s slug.

<?phpif (!defined('DIRECT_ACCESS')) {    header('Location: ../../');    exit();}//Edit this variable with the static page Slug/Url of the page you want to include in the block$block_static = 'serviceinc';/*** Pixie: The Small, Simple, Site Maker.* * Licence: GNU General Public License v3* Copyright (C) 2010, Scott Evans** This program is free software: you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation, either version 3 of the License, or* (at your option) any later version.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** You should have received a copy of the GNU General Public License* along with this program. If not, see http://www.gnu.org/licenses/** Title: Static Block** @package Pixie* @copyright 2011 David Benson* @author David Benson* @link http://blog.absolutedisaster.co.uk/pixie-user-editable-blocks* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3**/if ($page_type != 'static') {    include('admin/modules/static.php');}extract(safe_row('*', 'pixie_core', "page_name='$block_static'"));?><div id="block_static" class="block">    <div class="block_header">        <h4><?php echo $page_display_name;?></h4>    </div>    <div class="block_body">        <?php echo $page_content;?>    </div>    <div class="block_footer"></div></div><?php$s = $page_s;?>

You can use this method to create as many different editable blocks as you need – just change the $block_static value and the filename. You can probably adapt the same method pretty easily to use dynamic content with a bit of tweaking – I may look at that another day. I may not. Depends how far I go with Pixie.

Hmm. I know a couple of girls named Pixie. I should probably rephrase that…

[update201105251920]Later that evening I improved this slightly, so that the $block_static takes its value from the filename. This means that anyone with ftp access can very quickly and easily add a new block. Just keep a spare copy in the admin/blocks folder – possibly name it block_rename_me.php so it’s easily recognised, then site admins can copy and rename your master block_rename_me.php file to block_{newblock}.php where {newblock} is the name of the… new… block.

The changes:

Replace:

//Edit this variable with the static page Slug/Url of the page you want to include in the block$block_static = 'serviceinc';

with:

$block_static = substr(strrchr(__FILE__, '/'), 1);$block_static = substr($block_static, 6); $block_static = substr($block_static, 0, strpos($block_static, ".php"));

You can also replace:

<div id="block_static" class="block">

with:

<div id="block_<?php echo $block_static;?>" class="block">

and you now have a dynamic id you can target for styling and manipulation.

Complete with changes, with the $block_static definition moved for tidiness and consistency:

<?phpif (!defined('DIRECT_ACCESS')) {    header('Location: ../../');    exit();}/*** Pixie: The Small, Simple, Site Maker.* * Licence: GNU General Public License v3* Copyright (C) 2010, Scott Evans** This program is free software: you can redistribute it and/or modify* it under the terms of the GNU General Public License as published by* the Free Software Foundation, either version 3 of the License, or* (at your option) any later version.** This program is distributed in the hope that it will be useful,* but WITHOUT ANY WARRANTY; without even the implied warranty of* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the* GNU General Public License for more details.** You should have received a copy of the GNU General Public License* along with this program. If not, see http://www.gnu.org/licenses/** Title: Static Block** @package Pixie* @copyright 2011 David Benson* @author David Benson* @link http://blog.absolutedisaster.co.uk/pixie-user-editable-blocks* @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3**/$block_static = substr(strrchr(__FILE__, '/'), 1);$block_static = substr($block_static, 6); $block_static = substr($block_static, 0, strpos($block_static, ".php"));if ($page_type != 'static') {    include('admin/modules/static.php');}extract(safe_row('*', 'pixie_core', "page_name='$block_static'"));?><div id="block_<?php echo $block_static;?>" class="block">    <div class="block_header">        <h4><?php echo $page_display_name;?></h4>    </div>    <div class="block_body">        <?php echo $page_content;?>    </div>    <div class="block_footer"></div></div><?php$s = $page_s;?>

[/update]