Skip to content

Instantly share code, notes, and snippets.

@kevindew
Created January 21, 2011 00:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kevindew/788986 to your computer and use it in GitHub Desktop.
Save kevindew/788986 to your computer and use it in GitHub Desktop.
widgets/validators for sfPluploadPlugin
<?php
// an example within a sfForm configure method
class HomepageContentForm extends NavBarPageContentForm
{
public function configure()
{
// ...
$this->setWidget(
'contact_us_image_filename',
new sfWidgetFormInputPersistentFileEditablePlupload(
array(
'file_dir' =>
$this->getObject()->getImagesDirPublic('default'),
'plupload_options'
=> array(
'max_file_size' => '2MB',
'filters' => array(
array(
'title' => 'Image Files',
'extensions' => 'jpg,gif,png'
)
)
),
'is_image' => true,
'label' => 'Contact Us Image',
'plupload_upload_path'
=> sfContext::getInstance()->getController()
->genUrl(
'@sfPlupload?form=' . get_class($this)
. '&validator=contact_us_image_plupload'
. '&sf_format=json'
),
'with_delete' => false,
'minify_js_method'
=> array('sfCombineUtility', 'minifyInlineJs')
)
)
);
$this->setValidator(
'contact_us_image_filename',
new sfValidatorPersistentFile(
array(
'required' => true,
'mime_types' => 'web_images',
'path' => $this->getObject()->getImagesDirSystem('original'),
'post_save_processing_function'
=> array(
$this->getObject(),
'processContactUsImageTransformations'
),
'form' => $this,
'field_name' => 'contact_us_image_filename'
)
)
);
$this->setValidator(
'contact_us_image_plupload',
new sfValidatorPreUploadedFile(
array(
'required' => false,
'mime_types' => 'web_images',
'path' => $this->getObject()->getImagesDirSystem('original'),
'post_save_processing_function'
=> array(
$this->getObject(),
'processContactUsImageTransformations'
)
)
)
);
// ...
}
}
<?php
/**
* Validator for a file that persists between form submissions
*
* @package rbUtilityPlugin
* @subpackage validator
* @author Kevin Dew <kev@redbullet.co.uk>
* @version SVN $Id: sfValidatorPersistentFile.class.php 185 2010-12-09 21:29:43Z kevin $
*/
class sfValidatorPersistentFile extends sfValidatorUploadedFileAbstract
{
/**
* Configures the current validator.
*
* Available options:
*
* * form: A form object associated with this validator
* (to have it's tainted values updated)
* * field_name: The field name of the form field
* * file_validator_class: The class to validate the file upload
* (can't extend sfValidatorFile for this
* validator as otherwise the form processing
* on sfFormDoctrine tries to save it as a file)
* (Optional)
* * file_validator_options: Options for the file validator (Optional)
* * file_validator_messages: Messages for the file validator (Optional)
*
* @param array $options An array of options
* @param array $messages An array of error messages
*
* @see parent
*/
protected function configure($options = array(), $messages = array())
{
if (!ini_get('file_uploads'))
{
throw new LogicException(
sprintf(
'Unable to use a file validator as "file_uploads" is disabled in '
. 'your php.ini file (%s)', get_cfg_var('cfg_file_path')
)
);
}
parent::configure($options, $messages);
$this->addRequiredOption('form');
$this->addRequiredOption('field_name');
$this->addOption('check_existing_file', false);
$this->addOption('existing_file_path', null);
$this->addMessage('partial', 'The uploaded file was only partially uploaded.');
$this->addMessage('no_tmp_dir', 'Missing a temporary folder.');
$this->addMessage('cant_write', 'Failed to write file to disk.');
$this->addMessage('extension', 'File upload stopped by extension.');
}
/**
* @see parent
* @todo This doesn't currently delete any files, reason for this is we don't
* want to delete a file on a failed form processing and then have the
* user not save it leaving the old filename in storage
*/
protected function doClean($value)
{
if (!is_array($value))
{
throw new Exception('Array expected as value');
}
// whether user has specified to delete
$toDelete = isset($value['delete']) && $value['delete'];
$form = $this->getOption('form');
if (!$form instanceof sfForm)
{
throw new Exception('Form must be an instance of sfForm');
}
elseif (!method_exists($form, 'setTaintedValue'))
{
throw new Exception('Form must have a set tainted value method');
}
$filename = isset($value['file']) ? $value['file'] : '';
if (
isset($value['upload'])
&&
!(
isset($value['upload']['error'])
&&
UPLOAD_ERR_NO_FILE === $value['upload']['error']
)
)
{
// process the file sent
$validatedFile = $this->validateUploadedFile($value['upload']);
$filename = $this->processUpload($validatedFile);
$toDelete = false;
}
else if (
!$toDelete && $filename && $this->getOption('check_existing_file')
)
{
// check existing file
$this->checkExistingFile(
($this->getOption('existing_file_path')
? $this->getOption('existing_file_path')
: $this->getOption('path')
) . $filename
);
}
if ((!$filename || $toDelete) && $this->getOption('required'))
{
throw new sfValidatorError($this, 'required');
}
$return = $filename;
if ($toDelete)
{
$return = array(
'file' => $filename,
'delete' => true
);
}
$form->setTaintedValue($this->getOption('field_name'), $return);
return !$toDelete ? $filename : '';
}
protected function validateUploadedFile($upload)
{
if (!is_array($upload) || !isset($upload['tmp_name']))
{
throw new sfValidatorError(
$this, 'invalid', array('value' => (string) $upload)
);
}
if (!isset($upload['name']))
{
$upload['name'] = '';
}
if (!isset($upload['error']))
{
$upload['error'] = UPLOAD_ERR_OK;
}
if (!isset($upload['size']))
{
$upload['size'] = filesize($upload['tmp_name']);
}
if (!isset($upload['type']))
{
$upload['type'] = 'application/octet-stream';
}
switch ($upload['error'])
{
case UPLOAD_ERR_INI_SIZE:
$max = ini_get('upload_max_filesize');
if ($this->getOption('max_size'))
{
$max = min($max, $this->getOption('max_size'));
}
throw new sfValidatorError(
$this,
'max_size',
array('max_size' => $max, 'size' => (int) $upload['size'])
);
case UPLOAD_ERR_FORM_SIZE:
throw new sfValidatorError(
$this,
'max_size',
array('max_size' => 0, 'size' => (int) $upload['size'])
);
case UPLOAD_ERR_PARTIAL:
throw new sfValidatorError($this, 'partial');
case UPLOAD_ERR_NO_TMP_DIR:
throw new sfValidatorError($this, 'no_tmp_dir');
case UPLOAD_ERR_CANT_WRITE:
throw new sfValidatorError($this, 'cant_write');
case UPLOAD_ERR_EXTENSION:
throw new sfValidatorError($this, 'extension');
}
// check file size
if (
$this->hasOption('max_size')
&&
$this->getOption('max_size') < (int) $upload['size']
)
{
throw new sfValidatorError(
$this,
'max_size',
array(
'max_size' => $this->getOption('max_size'),
'size' => (int) $upload['size']
)
);
}
$mimeType = $this->getMimeType(
(string) $upload['tmp_name'], (string) $upload['type']
);
$mimeTypes = $this->getMimeTypes();
if (!$this->checkMimeType($mimeType, $mimeTypes))
{
throw new sfValidatorError(
$this,
'mime_types',
array('mime_types' => $mimeTypes, 'mime_type' => $mimeType)
);
}
$class = $this->getOption('validated_file_class');
return new $class(
$upload['name'],
$mimeType,
$upload['tmp_name'],
$upload['size'],
$this->getOption('path')
);
}
protected function checkExistingFile($filePath)
{
if (!file_exists($filePath))
{
throw new sfValidatorError(
$this,
'file_not_found'
);
}
$mimeType = $this->getMimeType($filePath, 'application/octet-stream');
$mimeTypes = $this->getMimeTypes();
if (!$this->checkMimeType($mimeType, $mimeTypes))
{
throw new sfValidatorError(
$this,
'mime_types',
array('mime_types' => $mimeTypes, 'mime_type' => $mimeType)
);
}
}
/**
* @see sfValidatorBase
*/
protected function isEmpty($value)
{
$noUploadedFile =
isset($value['upload']['error'])
&&
UPLOAD_ERR_NO_FILE === $value['upload']['error']
;
$emptyFile = !isset($value['file']) || !$value['file'];
$fileToBeDeleted = isset($value['delete']) && $value['delete'];
return
!is_array($value)
||
(
$noUploadedFile
&&
($emptyFile || $fileToBeDeleted)
)
;
}
}
<?php
/**
* Validator for a file that has been uploaded
*
* @package rbUtilityPlugin
* @subpackage validator
* @author Kevin Dew <kev@redbullet.co.uk>
* @version SVN $Id: sfValidatorPreUploadedFile.class.php 206 2010-12-15 10:52:42Z kevin $
*/
class sfValidatorPreUploadedFile extends sfValidatorUploadedFileAbstract
{
/**
* Configures the current validator.
*
* Available options:
*
* * return_with_filename: Return the validated result as an array with
* filename and the original filename
*
* @param array $options An array of options
* @param array $messages An array of error messages
*
* @see parent
*/
protected function configure($options = array(), $messages = array())
{
parent::configure($options, $messages);
$this->addOption('return_with_name', false);
}
/**
* @see parent
*/
protected function doClean($value)
{
if (!is_array($value))
{
$value['file'] = $value;
}
if (!isset($value['name']))
{
$value['name'] = '';
}
if (!isset($value['type']))
{
$value['type'] = 'application/octet-stream';
}
$value['size'] = sprintf('%u', filesize($value['file']));
// check file exists
if (!file_exists($value['file']))
{
throw new sfValidatorError(
$this,
'file_not_found'
);
}
// check mime type
$mimeType = $this->getMimeType($value['file'], $value['type']);
$mimeTypes = $this->getMimeTypes();
if (!$this->checkMimeType($mimeType, $mimeTypes))
{
throw new sfValidatorError(
$this,
'mime_types',
array('mime_types' => $mimeTypes, 'mime_type' => $mimeType)
);
}
// check max size
// check file size
if (
$this->hasOption('max_size')
&&
$this->getOption('max_size') < (int) $value['size']
)
{
throw new sfValidatorError(
$this,
'max_size',
array(
'max_size' => $this->getOption('max_size'),
'size' => (int) $value['size']
)
);
}
$class = $this->getOption('validated_file_class');
$validatedFile = new $class(
$value['name'],
$mimeType,
$value['file'],
$value['size'],
$this->getOption('path')
);
return $this->getOption('return_with_name')
? array(
'file' => $this->processUpload($validatedFile),
'name' => $value['name']
)
: $this->processUpload($validatedFile)
;
}
}
<?php
/**
* Validator for a uploaded file
*
* Uses code from sfValidatorFile
*
* @package rbUtilityPlugin
* @subpackage validator
* @author Kevin Dew <kev@redbullet.co.uk>
* @version SVN $Id: sfValidatorUploadedFileAbstract.class.php 185 2010-12-09 21:29:43Z kevin $
*/
abstract class sfValidatorUploadedFileAbstract extends sfValidatorBase
{
/**
* Configures the current validator.
*
* Available options:
*
* * generate_filename_function:
* A function to be called by call_user_func
* to generate the files filename (Optional)
* * generate_filename_function_args:
* An array of extra arguments for the above
* function (Optional)
* * post_save_processing_function:
* A function to be called by call_user_func
* to process the uploaded file (Optional)
* * post_save_processing_function_args:
* An array of extra arguments for the above
* function (Optional)
*
* @param array $options An array of options
* @param array $messages An array of error messages
*
* @see parent
*/
protected function configure($options = array(), $messages = array())
{
parent::configure($options, $messages);
$this->addOption('generate_filename_function', null);
$this->addOption('generate_filename_function_args', array());
$this->addOption('post_save_processing_function', null);
$this->addOption('post_save_processing_function_args', array());
$this->addOption('max_size');
$this->addOption('mime_types');
$this->addOption('mime_type_guessers', array(
array($this, 'guessFromFileinfo'),
array($this, 'guessFromMimeContentType'),
array($this, 'guessFromFileBinary'),
));
$this->addOption('mime_categories', array(
'web_images' => array(
'image/jpeg',
'image/pjpeg',
'image/png',
'image/x-png',
'image/gif',
)));
$this->addOption('validated_file_class', 'sfValidatedFile');
$this->addOption('path', null);
$this->addMessage(
'max_size', 'File is too large (maximum is %max_size% bytes).'
);
$this->addMessage('mime_types', 'Invalid mime type (%mime_type%).');
$this->addMessage(
'file_not_found', 'File was not found'
);
}
protected function generateFilename($originalFilename)
{
$generatedFilename = null;
if ($this->getOption('generate_filename_function'))
{
if (!is_callable($this->getOption('generate_filename_function')))
{
throw new Exception('Generate filename function is not callable');
}
$generatedFilename = call_user_func_array(
$this->getOption('generate_filename_function'),
array_merge(
array($originalFilename),
$this->getOption('generate_filename_function_args')
)
);
}
return $generatedFilename;
}
protected function postSaveProcessing($filePath)
{
if ($this->getOption('post_save_processing_function'))
{
if (!is_callable($this->getOption('post_save_processing_function')))
{
throw new Exception('Post save processing function is not callable');
}
call_user_func_array(
$this->getOption('post_save_processing_function'),
array_merge(
array($filePath),
$this->getOption('post_save_processing_function_args')
)
);
}
}
/**
* Returns the mime type of a file.
*
* This methods call each mime_type_guessers option callables to
* guess the mime type.
*
* This method always returns a lower-cased string as mime types are case-insensitive
* as per the RFC 2616 (http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7).
*
* @param string $file The absolute path of a file
* @param string $fallback The default mime type to return if not guessable
*
* @return string The mime type of the file (fallback is returned if not guessable)
*/
protected function getMimeType($file, $fallback)
{
foreach ($this->getOption('mime_type_guessers') as $method)
{
$type = call_user_func($method, $file);
if (null !== $type && $type !== false)
{
return strtolower($type);
}
}
return strtolower($fallback);
}
/**
* Guess the file mime type with PECL Fileinfo extension
*
* @param string $file The absolute path of a file
*
* @return string The mime type of the file (null if not guessable)
*/
protected function guessFromFileinfo($file)
{
if (!function_exists('finfo_open') || !is_readable($file))
{
return null;
}
if (!$finfo = new finfo(FILEINFO_MIME))
{
return null;
}
$type = $finfo->file($file);
// remove charset (added as of PHP 5.3)
if (false !== $pos = strpos($type, ';'))
{
$type = substr($type, 0, $pos);
}
return $type;
}
/**
* Guess the file mime type with mime_content_type function (deprecated)
*
* @param string $file The absolute path of a file
*
* @return string The mime type of the file (null if not guessable)
*/
protected function guessFromMimeContentType($file)
{
if (!function_exists('mime_content_type') || !is_readable($file))
{
return null;
}
return mime_content_type($file);
}
/**
* Guess the file mime type with the file binary (only available on *nix)
*
* @param string $file The absolute path of a file
*
* @return string The mime type of the file (null if not guessable)
*/
protected function guessFromFileBinary($file)
{
ob_start();
//need to use --mime instead of -i. see #6641
passthru(sprintf('file -b --mime %s 2>/dev/null', escapeshellarg($file)), $return);
if ($return > 0)
{
ob_end_clean();
return null;
}
$type = trim(ob_get_clean());
if (!preg_match('#^([a-z0-9\-]+/[a-z0-9\-]+)#i', $type, $match))
{
// it's not a type, but an error message
return null;
}
return $match[1];
}
protected function getMimeTypesFromCategory($category)
{
$categories = $this->getOption('mime_categories');
if (!isset($categories[$category]))
{
throw new InvalidArgumentException(
sprintf('Invalid mime type category "%s".', $category)
);
}
return $categories[$category];
}
protected function getMimeTypes()
{
if ($this->hasOption('mime_types'))
{
$mimeTypes = is_array($this->getOption('mime_types'))
? $this->getOption('mime_types')
: $this->getMimeTypesFromCategory($this->getOption('mime_types')
);
return $mimeTypes;
}
return array();
}
protected function checkMimeType($mimeType, $mimeTypes)
{
if ($mimeTypes)
{
return in_array($mimeType, array_map('strtolower', $mimeTypes));
}
return true;
}
/**
* Saves the file and performs any actions specified for it
*
* @param array $upload an array of data for a file upload
* @return string
*/
protected function processUpload(sfValidatedFile $validatedFile)
{
$filePath = rtrim($this->getOption('path'), '/');
$generatedFilename = $this->generateFilename(
isset($upload['name']) ? $upload['name'] : null
);
$filename = $validatedFile->save($generatedFilename);
$this->postSaveProcessing($filePath . '/' . $filename);
return $filename;
}
}
<?php
/**
* Widget for a file that persists between form submissions
*
* @package rbUtilityPlugin
* @subpackage widget
* @author Kevin Dew <kev@redbullet.co.uk>
* @version SVN $Id: sfWidgetFormInputPersistentFileEditable.class.php 206 2010-12-15 10:52:42Z kevin $
*/
class sfWidgetFormInputPersistentFileEditable extends sfWidgetFormInputFile
{
protected
$_containerId,
$_hiddenFieldName,
$_hiddenFieldId,
$_file,
$_toDelete,
$_deleteId
;
/**
* Constructor.
*
* Available options:
*
* * file_dir: The directory where the file is (required)
* * is_image: Whether the file is a displayable image
* * with_delete: Whether to add a delete checkbox or not
* * delete_label: The delete label used by the template
* * template: The HTML template to use to render this widget when in edit mode
* The available placeholders are:
* * %input% (the image upload widget)
* * %delete% (the delete checkbox)
* * %delete_label% (the delete label text)
* * %file% (the file tag)
*
* @param array $options An array of options
* @param array $attributes An array of default HTML attributes
*
* @see sfWidgetFormInputFile
*/
protected function configure($options = array(), $attributes = array())
{
parent::configure($options, $attributes);
$this->addRequiredOption('file_dir');
$this->addOption('is_image', false);
$this->addOption('with_delete', true);
$this->addOption('delete_label', 'Remove');
$this->addOption(
'template',
'<div id="%container_id%" class="editable-file-container">'
. '<div class="file-placeholder">%file_template%</div> '
. '%input_template%'
. '%delete_template%'
. '</div> %javascript%'
);
$this->addOption('to_delete_class', 'delete');
$this->addOption(
'file_template',
'<div class="file-upload file-container%to_delete_class%">'
. '<a href="%file_url%" target="_blank" class="file">%file_name%</a>'
. '</div>'
);
$this->addOption(
'image_template',
'<div class="file-upload image-container%to_delete_class%">'
. '<img src="%file_url%" alt="" />'
. '</div>'
);
$this->addOption(
'input_template',
'<div class="input-container">'
. '%file_input% %hidden_input%'
. '</div>'
);
$this->addOption(
'delete_template',
'<div class="delete-container">'
. '%delete_input% %delete_label%'
. '</div>'
);
$this->addOption('minify_js_method', null);
$this->addOption('minify_js_method_options', array());
$this->addOption(
'javascript',
<<<EOF
(function($) {
$(document).ready(function() {
$("#%delete_id%").click(function() {
if (this.checked) {
$("#%container_id%").find(".file").addClass("%to_delete_class%");
} else {
$("#%container_id%").find(".file").removeClass("%to_delete_class%");
}
});
});
})(jQuery);
EOF
);
}
/**
* Renders the widget.
*
* @param string $name The element name
* @param string $value The value displayed in this widget
* @param array $attributes An array of HTML attributes to be merged with the default HTML attributes
* @param array $errors An array of errors for the field
*
* @return string An HTML tag string
*
* @see sfWidgetForm
*/
public function render($name, $value = null, $attributes = array(), $errors = array())
{
$this->_containerId = $this->generateId($name . '[container]');
$this->_hiddenFieldName = $name . '[file]';
$this->_hiddenFieldId = $this->generateId($this->_hiddenFieldName);
$this->_file = $value;
$this->_toDelete = false;
$this->_deleteId = $this->generateId($name . '[delete]');
if (is_array($value))
{
$this->_file = isset($value['file']) ? $value['file'] : '';
$this->_toDelete = isset($value['delete']) && $value['delete'];
}
return strtr(
$this->getOption('template'),
array(
'%container_id%' =>
$this->_containerId,
'%file_template%' =>
$this->buildFileTemplate($name, $value, $attributes, $errors),
'%input_template%' =>
$this->buildInputTemplate($name, $value, $attributes, $errors),
'%delete_template%' =>
$this->buildDeleteTemplate($name, $value, $attributes, $errors),
'%javascript%' =>
$this->buildJavascript($name, $value, $attributes, $errors)
)
);
}
protected function buildFileTemplate($name, $value, $attributes, $errors)
{
if (!$this->_file)
{
return '';
}
$filePath = '';
if ($this->getOption('file_dir'))
{
$filePath = rtrim($this->getOption('file_dir'), '/');
}
$filePath .= '/' . $this->_file;
$deleteClass =
($this->getOption('to_delete_class') ? ' ' : '')
. $this->getOption('to_delete_class')
;
$replacements = array(
'%file_name%' => $this->_file,
'%file_url%' => $filePath,
'%to_delete_class%' => $this->_toDelete ? $deleteClass : ''
);
if ($this->getOption('is_image'))
{
$template = strtr($this->getOption('image_template'), $replacements);
}
else
{
$template = strtr($this->getOption('file_template'), $replacements);
}
return $template;
}
protected function buildInputTemplate($name, $value, $attributes, $errors)
{
$fileInput = $this->renderTag(
'input',
array('type' => 'file', 'name' => $name . '[upload]')
);
$hiddenInput = '';
if ($this->_file)
{
$hiddenInput = $this->renderTag(
'input',
array(
'type' => 'hidden',
'name' => $this->_hiddenFieldName,
'id' => $this->_hiddenFieldId,
'value' => $this->_file
)
);
}
return strtr(
$this->getOption('input_template'),
array(
'%file_input%' => $fileInput,
'%hidden_input%' => $hiddenInput
)
);
}
protected function buildDeleteTemplate($name, $value, $attributes, $errors)
{
if (!$this->getOption('with_delete') || !$this->_file)
{
return '';
}
$deleteInput = $this->renderTag(
'input',
array_merge(
array('type' => 'checkbox', 'name' => $name . '[delete]'),
$attributes,
array('checked' => $this->_toDelete)
)
);
$deleteLabel = $this->renderContentTag(
'label',
$this->translate($this->getOption('delete_label')),
array_merge(array('for' => $this->generateId($name . '[delete]')))
);
return strtr(
$this->getOption('delete_template'),
array(
'%delete_input%' => $deleteInput,
'%delete_label%' => $deleteLabel
)
);
}
protected function buildJavascript($name, $value, $attributes, $errors)
{
if (!$this->getOption('javascript'))
{
return '';
}
$javascript = strtr(
$this->getOption('javascript'),
array(
'%delete_id%' => addcslashes($this->_deleteId, '"'),
'%container_id%' => addcslashes($this->_containerId, '"'),
'%to_delete_class%' => addcslashes($this->getOption('to_delete_class'), '"'),
)
);
return sprintf(<<<EOF
<script type="text/javascript">
//<![CDATA[
%s
//]]>
</script>
EOF
,
$this->minifyJavascript($javascript)
);
}
/**
* @see parent
* @return array
*/
public function getStylesheets()
{
return array(
'/rbUtilityPlugin/css/input-persistent-file.css' => 'screen'
);
}
protected function minifyJavascript($javascript)
{
if (
$this->getOption('minify_js_method')
&&
is_callable($this->getOption('minify_js_method'))
)
{
$javascript = call_user_func(
$this->getOption('minify_js_method'),
$javascript,
$this->getOption('minify_js_method_options')
);
}
return $javascript;
}
}
<?php
/**
* Widget for a file that persists between form submissions and uses Plupload
* as the mechanism for uploading
*
* Note default options use both jQuery and jQuery UI
*
* @package rbUtilityPlugin
* @subpackage widget
* @author Kevin Dew <kev@redbullet.co.uk>
* @version SVN $Id: sfWidgetFormInputPersistentFileEditablePlupload.class.php 218 2010-12-21 01:00:15Z kevin $
*/
class sfWidgetFormInputPersistentFileEditablePlupload
extends sfWidgetFormInputPersistentFileEditable
{
protected
$_containerId,
$_uploaderId,
$_hiddenFieldName,
$_hiddenFieldId,
$_file,
$_toDelete,
$_pluploadOptions,
$_deleteId,
$_pluploadDefaultOptions
;
/**
* There are a lot of options!
*
* Required
* * plupload_upload_path - the path that files will be uploaded to
*
* Optional
* * plupload_tempate - html for the plupload controls
* * uploader_text - text within the uploader control
* * uploading_text - text that appears while file is uploading
* * ul_error_class - class for the list holding errors
* * li_error_class - class for the list item within it
* * plupload_500_error_message - error message for when plupload is returned
* a 500 error
* * plupload_file_size_error_message
* * plupload_extension_error_message
* * delete_active_class - a class to give the delete container when it is in
* use so javascript can determine whether to hide it or not
* * plupload_options - options for plupload itself, this is merged with
* the default options specified in this class and
* app_rbUtilityPlugin_plupload_options in a app.yml
* * plupload_path_to_js - path to the javascript files for plupload
* * plupload_js_base - base javascript for this widget (glues other parts in
* it)
* * delete_js - js for handling the delete checkbox
* * plupload_js_pre_init - all of these are various js methods
* * plupload_js_files_added
* * plupload_js_upload_progress
* * plupload_js_error
* * plupload_js_file_uploaded
* * plupload_js_reset_uploader
* * plupload_js_handle_error
* * plupload_js_handle_success
* * plupload_js_hide_uploader
* * plupload_js_reset_errors
* * plupload_js_post_init
*
* @see parent
* @param array $options
* @param array $attributes
*/
protected function configure($options = array(), $attributes = array())
{
parent::configure($options, $attributes);
// default options for plupload
$this->_pluploadDefaultOptions = $this->getDefaultPluploadOptions();
$this->addRequiredOption('plupload_upload_path');
$this->addOption(
'template',
'<div id="%container_id%" class="editable-file-container plupload-editable">'
. '<div class="file-placeholder">%file_template%</div> '
. '%plupload_template% '
. '%input_template%'
. '%delete_template%'
. '<div class="errors"></div>'
. '</div> %javascript%'
);
$this->addOption(
'plupload_template',
'<div class="plupload-controls-container">'
. '<div class="progress-container"></div>'
. '<div class="uploader-container" id="%plupload_container_id%">'
. '<button class="uploader" id="%uploader_id%">%uploader_text%</button>'
. '</div>'
. '</div>'
);
$this->addOption('uploader_text', 'Upload');
$this->addOption('uploading_text', 'Uploading %filename%');
$this->addOption('ul_error_class', 'form-errors sf-error');
$this->addOption('li_error_class', 'error');
$this->addOption(
'plupload_500_error_message', 'File could not be uploaded'
);
$this->addOption(
'plupload_file_size_error_message', 'File size is too big'
);
$this->addOption(
'plupload_extension_error_message', 'File is incorrect type'
);
$this->addOption(
'delete_template',
'<div class="delete-container%delete_active_class%">'
. '%delete_input% %delete_label%'
. '</div>'
);
$this->addOption('delete_active_class', 'active');
$this->addOption('plupload_options', array());
$this->addOption(
'plupload_path_to_js', '/rbUtilityPlugin/js/plupload-1.3.0'
);
$this->addOption('plupload_js_base', <<<EOF
(function($) {
$(document).ready(function() {
%delete_js%
%plupload_js_pre_init%
var container = $("#%container_id%");
$("#%uploader_id%").click(function() { return false; });
container.find(".input-container").hide();
container.find(".delete-container:not(.active)").hide();
if (!$("#%hidden_field_id%").length) {
$("<input type=\"hidden\" name=\"%hidden_field_name%\" "
+ "id=\"%hidden_field_id%\" />"
).appendTo(container.find(".input-container").first());
}
var uploader = new plupload.Uploader(%uploader_json%);
uploader.init();
uploader.bind("FilesAdded", function(up, files) {
%plupload_js_files_added%
});
uploader.bind("UploadProgress", function(up, file) {
%plupload_js_upload_progress%
});
uploader.bind("Error", function(up, err) {
%plupload_js_error%
});
uploader.bind("FileUploaded", function(up, file, response) {
%plupload_js_file_uploaded%
});
function resetUploader(up) {
%plupload_js_reset_uploader%
}
function handleError(up, message) {
%plupload_js_handle_error%
}
function handleSuccess(up, file) {
%plupload_js_handle_success%
}
function hideUploader(up) {
%plupload_js_hide_uploader%
}
function resetErrors(up) {
%plupload_js_reset_errors%
}
});
})(jQuery);
EOF
);
$this->addOption('delete_js', <<<EOF
$("#%delete_id%").click(function() {
if (this.checked) {
$("#%container_id%").find(".file").addClass("%to_delete_class%");
} else {
$("#%container_id%").find(".file").removeClass("%to_delete_class%");
}
});
EOF
);
$this->addOption('plupload_js_pre_init', '');
$this->addOption('plupload_js_files_added', <<<EOF
resetErrors();
resetUploader(up);
var filename = files[0].name;
var progress = container.find(".progress-container").get(0);
$(progress).append(
"<div class=\"progress\"><div class=\"message\">%uploading_text%"
+ "</div><div class=\"bar\"></div></div>"
);
$(progress).find(".bar").progressbar({value: 0});
hideUploader(up);
up.start();
up.refresh();
EOF
);
$this->addOption('plupload_js_upload_progress', <<<EOF
container.find(".progress-container .bar").progressbar(
"value", file.percent
);
EOF
);
$this->addOption('plupload_js_error', <<<EOF
var message = "";
switch (err.code) {
case plupload.FILE_SIZE_ERROR:
message = "%plupload_file_size_error_message%";
break;
case plupload.FILE_EXTENSION_ERROR:
message = "%plupload_extension_error_message%";
break;
}
return handleError(up, message);
EOF
);
$this->addOption('plupload_js_file_uploaded', <<<EOF
try {
var json = $.parseJSON(response.response);
} catch (err) {
return handleError(up, "");
}
if (json.status == "error") {
return handleError(up, json.message);
}
if (json.status != "complete") {
return;
}
handleSuccess(up, json.file);
EOF
);
$this->addOption('plupload_js_reset_uploader', <<<EOF
container.find(".progress-container").empty();
container.find(".uploader-container button.uploader").show();
up.refresh();
EOF
);
$this->addOption('plupload_js_handle_error', <<<EOF
if (!message) {
message = "%plupload_500_error_message%";
}
resetErrors(up);
var ul = $("<ul class=\"%ul_error_class%\" />").append(
$("<li class=\"%li_error_class%\" />").text(message)
);
container.find(".errors").append(ul);
resetUploader(up);
EOF
);
$this->addOption('plupload_js_handle_success', <<<EOF
container.find(".file-placeholder .file").remove();
var template = "%file_template%";
template = template.replace(/%file_url%/g, "%path%" + "/" + file);
template = template.replace(/%file_name%/g, file);
container.find(".file-placeholder").append(template);
if ($("#%hidden_field_id%").length) {
$("#%hidden_field_id%").val(file);
} else {
$("<input type=\"hidden\" name=\"%hidden_field_name%\" "
+ "id=\"%hidden_field_id%\" />"
).val(file).appendTo(container.find(".input-container").first());
}
container.find(".delete-container").show();
resetUploader(up);
EOF
);
$this->addOption('plupload_js_hide_uploader', <<<EOF
container.find(".uploader-container button.uploader").hide();
up.refresh();
EOF
);
$this->addOption('plupload_js_reset_errors', <<<EOF
container.find(".errors").empty();
EOF
);
$this->addOption('plupload_js_post_init', '');
}
/**
* Renders the widget.
*
* @param string $name The element name
* @param string $value The value displayed in this widget
* @param array $attributes An array of HTML attributes to be merged with the default HTML attributes
* @param array $errors An array of errors for the field
*
* @return string An HTML tag string
*
* @see sfWidgetForm
*/
public function render($name, $value = null, $attributes = array(), $errors = array())
{
$this->_containerId = $this->generateId($name . '[container]');
$this->_pluploadContainerId =
$this->generateId($name . '[plupload_container]')
;
$this->_uploaderId = $this->generateId($name . '[uploader]');
$this->_deleteId = $this->generateId($name . '[delete]');
$this->_hiddenFieldName = $name . '[file]';
$this->_hiddenFieldId = $this->generateId($this->_hiddenFieldName);
// make plupload options
$this->_pluploadOptions = array_merge(
$this->getPluploadOptions(),
array(
'url' => $this->getOption('plupload_upload_path'),
'browse_button' => $this->_uploaderId,
'container' => $this->_pluploadContainerId
)
);
$this->_file = $value;
$this->_toDelete = false;
if (is_array($value))
{
$this->_file = isset($value['file']) ? $value['file'] : '';
$this->_toDelete = isset($value['delete']) && $value['delete'];
}
return strtr(
$this->getOption('template'),
array(
'%container_id%' =>
$this->_containerId,
'%file_template%' =>
$this->buildFileTemplate($name, $value, $attributes, $errors),
'%plupload_template%' =>
$this->buildPluploadTemplate($name, $value, $attributes, $errors),
'%input_template%' =>
$this->buildInputTemplate($name, $value, $attributes, $errors),
'%delete_template%' =>
$this->buildDeleteTemplate($name, $value, $attributes, $errors),
'%javascript%' =>
$this->buildJavascript($name, $value, $attributes, $errors),
)
);
}
/**
* Build the template for the plupload part of the widget
*
* @param string $name
* @param string $value
* @param array $attributes
* @param array $errors
* @return string
*/
protected function buildPluploadTemplate($name, $value, $attributes, $errors)
{
return strtr(
$this->getOption('plupload_template'),
array(
'%plupload_container_id%' => $this->_pluploadContainerId,
'%uploader_id%' => $this->_uploaderId,
'%uploader_text%' => $this->getOption('uploader_text')
)
);
}
/**
* Build the template for the delete part of the widget
*
* @param string $name
* @param string $value
* @param array $attributes
* @param array $errors
* @return string
*/
protected function buildDeleteTemplate($name, $value, $attributes, $errors)
{
if (!$this->getOption('with_delete'))
{
return '';
}
$deleteInput = $this->renderTag(
'input',
array_merge(
array('type' => 'checkbox', 'name' => $name . '[delete]'),
$attributes,
array('checked' => $this->_toDelete)
)
);
$deleteLabel = $this->renderContentTag(
'label',
$this->translate($this->getOption('delete_label')),
array_merge(array('for' => $this->generateId($name . '[delete]')))
);
return strtr(
$this->getOption('delete_template'),
array(
'%delete_input%' => $deleteInput,
'%delete_label%' => $deleteLabel,
'%delete_active_class%' => ($this->_file
? ' ' . $this->getOption('delete_active_class')
: ''
)
)
);
}
/**
* Build the javascript for this widget
*
* @param string $name
* @param string $value
* @param array $attributes
* @param array $errors
* @return string
*/
protected function buildJavascript($name, $value, $attributes, $errors)
{
// build full js for translatation
$javascript = strtr(
$this->getOption('plupload_js_base'),
array(
'%delete_js%' => $this->getOption('delete_js'),
'%plupload_js_pre_init%' => $this->getOption('plupload_js_pre_init'),
'%plupload_js_files_added%' => $this->getOption('plupload_js_files_added'),
'%plupload_js_upload_progress%' => $this->getOption('plupload_js_upload_progress'),
'%plupload_js_error%' => $this->getOption('plupload_js_error'),
'%plupload_js_file_uploaded%' => $this->getOption('plupload_js_file_uploaded'),
'%plupload_js_reset_uploader%' => $this->getOption('plupload_js_reset_uploader'),
'%plupload_js_handle_error%' => $this->getOption('plupload_js_handle_error'),
'%plupload_js_handle_success%' => $this->getOption('plupload_js_handle_success'),
'%plupload_js_hide_uploader%' => $this->getOption('plupload_js_hide_uploader'),
'%plupload_js_reset_errors%' => $this->getOption('plupload_js_reset_errors'),
'%plupload_js_post_init%' => $this->getOption('plupload_js_post_init'),
)
);
$fileTemplate = $this->getOption('is_image')
? $this->getOption('image_template')
: $this->getOption('file_template')
;
$uploadingText = strtr(
addcslashes($this->getOption('uploading_text'), '"'),
array(
'%filename%' => '" + filename + "'
)
);
// add replacement values
$javascript = strtr(
$javascript,
array(
'%uploader_id%' => addcslashes($this->_uploaderId, '"'),
'%container_id%' => addcslashes($this->_containerId, '"'),
'%delete_id%' => addcslashes($this->_deleteId, '"'),
'%uploader_json%' => json_encode($this->_pluploadOptions),
'%file_template%' => addcslashes(
strtr($fileTemplate, array('%to_delete_class%' => ''))
, '"'
),
'%path%' => addcslashes(rtrim($this->getOption('file_dir'), '/'), '"'),
'%hidden_field_name%' => addcslashes($this->_hiddenFieldName, '"'),
'%hidden_field_id%' => addcslashes($this->_hiddenFieldId, '"'),
'%ul_error_class%' =>
addcslashes($this->getOption('ul_error_class', ''), '"'),
'%li_error_class%' =>
addcslashes($this->getOption('li_error_class', ''), '"'),
'%plupload_500_error_message%' =>
addcslashes($this->getOption('plupload_500_error_message', ''), '"'),
'%plupload_file_size_error_message%' =>
addcslashes(
$this->getOption('plupload_file_size_error_message'), '"'
),
'%plupload_extension_error_message%' =>
addcslashes(
$this->getOption('plupload_extension_error_message'), '"'
),
'%to_delete_class%' => addcslashes($this->getOption('to_delete_class'), '"'),
'%uploading_text%' => $uploadingText
)
);
return sprintf(<<<EOF
<script type="text/javascript">
//<![CDATA[
%s
//]]>
</script>
EOF
,
$this->minifyJavascript($javascript)
);
}
/**
* Get the javascripts for this widget
*
* @see parent
* @return array
*/
public function getJavaScripts()
{
$options = $this->getPluploadOptions();
$javascripts = array();
if (
isset($options['runtimes'])
&&
strpos($options['runtimes'], 'gears') !== false
)
{
$javascripts[] =
$this->getOption('plupload_path_to_js') . '/gears_init.js'
;
}
$javascripts[] =
$this->getOption('plupload_path_to_js') . '/plupload.full.min.js'
;
return array_merge(
parent::getJavaScripts(),
$javascripts
);
}
/**
* Build and return plupload options
*
* @return array
*/
protected function getPluploadOptions()
{
return array_merge(
array(
'flash_swf_url' =>
$this->getOption('plupload_path_to_js') . '/plupload.flash.swf',
'silverlight_xap_url' =>
$this->getOption('plupload_path_to_js') . '/plupload.silverlight.xap'
),
$this->_pluploadDefaultOptions,
sfConfig::get('app_rbUtilityPlugin_plupload_options', array()),
$this->getOption('plupload_options')
);
}
/**
* Get the default options for plupload
*
* @return array
*/
public function getDefaultPluploadOptions()
{
return array(
'runtimes' => 'gears,flash,html5,silverlight',
'chunk_size' => '1MB',
'multi_selection' => false,
'unique_names' => true
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment