
eZ Publish Workflow - electronic media delivery
I’m helping on a site that sells music.
I wanted a way to bundle a person’s purchases into a single file that could be downloaded once. There are two types of electronic media, mp3_product and album. Each may have an image associated with it. An mp3_product is an .mp3 files, an album is a .zip file, containing .mp3s.
The link above is a great resource. Using it, I created a custom workflow:
<?php
class CreateDownloadFileType extends eZWorkflowEventType
{
const WORKFLOW_TYPE_STRING = "createdownloadfile";
public function __construct()
{
parent::__construct( CreateDownloadFileType::WORKFLOW_TYPE_STRING, 'Create Download File');
}
public function execute( $process, $event )
{
$parameters = $process->attribute( 'parameter_list' );
$order_id = $parameters['order_id'];
$user_id = $parameters['user_id'];
$order = eZShopFunctionCollection::fetchOrder ($order_id);
$items = eZProductCollectionItem::fetchList( array( 'productcollection_id' => $order['result']->ProductCollectionID ) );
$order_number = $order['result']->OrderNr;
$zip_file='/home/account/www/download/'.md5($order_number.$user_id).'.zip';
$zip = new ZipArchive();
if ($zip->open($zip_file,ZIPARCHIVE::CREATE)!==true) {
eZDebug::writeError('Order: '.$order_id.' - could not create zip file '.$zip_file);
}
$storageDir = eZSys::storageDirectory();
$attributes_to_include = array('file','image');
foreach ($items as $item)
{
$product = eZContentObject::fetch($item->ContentObjectID);
if (in_array($product->ClassIdentifier,array('mp3_product','album')))
{
$product_data = $product->dataMap();
$name = preg_replace('/[^\w]/','_',trim($product_data['name']->DataText));
foreach ($attributes_to_include as $v)
{
if ($product_data[$v]!==null)
{
$attribute_data = $product_data[$v]->Content();
$file = $attribute_data->Filename;
$mime_type = $attribute_data->MimeType;
list( $group, $type ) = explode( '/', $mime_type );
$filePath = $storageDir . '/original/' . $group . '/' . $file;
$pathInfo = pathinfo($filePath);
if ($zip->addFile($filePath,$name.'.'.$pathInfo['extension'])!==true) {
eZDebug::writeError('Order: '.$order_id.' - failed to add '.$filePath.' to '.$zip_file);
}
}
}
}
$zip->close();
}
return eZWorkflowType::STATUS_ACCEPTED;
}
}
eZWorkflowEventType::registerEventType( CreateDownloadFileType::WORKFLOW_TYPE_STRING, 'createdownloadfiletype' );
?>
The zip file created is named with an md5 of the order number and user id, and placed in a download directory. It’s probably not the most secure approach, but I think it is okay for this application, and it can be changed later.
This triggers on shop/checkout/after, which means the person has paid.
The download link for the zip file is placed in both the order view and order email templates.
{def $current_user=fetch( 'user', 'current_user' )}
{if $current_user.is_logged_in}
<h2>{"Download"|i18n("design/base/shop")}:</h2>
<p><a href="/download/{concat($order.order_nr,$current_user.contentobject_id)|md5()}.zip">{"Click to download"|i18n("design/base/shop")}</
a></p>
{/if}
{undef $current_user}
You may need an Alias and a RewriteRule:
Alias download /home/account/www/download
RewriteRule ^download/.*\.zip - [L]
The final step was to create a mechanism to delete the files after they are downloaded. There are at least two ways to do this. The first approach is to use an Apache filter, with a bash script:
Apache configuration code:
ExtFilterDefine delete mode=output
cmd="/home/account/webfilter.sh"
<Directory /home/account/www/download>
SetOutputFilter delete
</Directory>
FILENAME="/home/account/www$DOCUMENT_URI";
cat -
if [ -e $FILENAME ]; then
rm $FILENAME;
fi
The Apache filter code is courtesy of Lyrix, Inc..
If you can’t use a filter - which is likely if you are using shared hosting, you can also use a PHP script. This uses a RewriteRule to route the request through PHP.
Options -Indexes
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^(.*\.zip)$ zip.php?path=$1 [L]
</IfModule>
Finally, this is the PHP code that will deliver the file, then delete it.
<?php
// downloading a file and then deleting it
$filename = $_GET['path'];
if (is_file($filename))
{
// fix for IE catching or PHP bug issue
header("Pragma: public");
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: public");
header("Content-Description: File Transfer");
// browser must download file from server instead of cache
// force download dialog
header("Content-Type: application/zip");
// use the Content-Disposition header to supply a recommended filename and
// force the browser to display the save dialog.
header("Content-Disposition: attachment; filename=".basename($filename).";");
/*
The Content-transfer-encoding header should be binary, since the file will be read
directly from the disk and the raw bytes passed to the downloading computer.
The Content-length header is useful to set for downloads. The browser will be able to
show a progress meter as a file downloads. The content-lenght can be determines by
filesize function returns the size of a file.
*/
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($filename));
@readfile($filename);
unlink($filename);
exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>Requested Content Not Available</title>
</head>
<body>
<h1>Requested Content Not Available</h1>
<p>The content you requested is not available.</p>
<p>If you were unable to download your purchases, please contact us for assistance.</p>
</body>
</html>
Print article | This entry was posted by elvis on 08/07/12 at 07:40:00 pm . Follow any responses to this post through RSS 2.0. |