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…
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.
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!
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