MENU_CALLBACK,
'page callback' => 'comment_upload_js',
'access arguments' => array('upload files to comments'),
);
return $items;
}
/**
* Implementation of hook_perm().
*/
function comment_upload_perm() {
return array('upload files to comments');
}
function comment_upload_theme() {
return array(
'comment_upload_attachments' => array(
'arguments' => array('files' => NULL),
),
'comment_upload_form_current' => array(
'arguments' => array('form' => NULL),
),
'comment_upload_form_new' => array(
'arguments' => array('form' => NULL),
),
);
}
/**
* Add an Attachments for comments setting to the content type settings forms.
*
* Implementation of hook_form_alter().
*/
function comment_upload_form_alter(&$form, $form_state, $form_id) {
if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
$form['comment']['comment_upload'] = array(
'#type' => 'radios',
'#title' => t('Attachments on comments'),
'#default_value' => variable_get('comment_upload_'. $form['#node_type']->type, 1),
'#options' => array(t('Disabled'), t('Enabled')), '#weight' => 20,
);
}
}
/**
* Hook into the comment_form to support file uploads.
*
* Implementation of hook_form_form-id_alter().
*/
function comment_upload_form_comment_form_alter(&$form, &$form_state) {
if (!user_access('upload files to comments')) {
return;
}
// Check whether attachments are enabled for comments on this content type.
$node = node_load($form['nid']['#value']);
if (!variable_get('comment_upload_'. $node->type, 0)) {
return;
}
$files = array();
$cid = $form['cid']['#value'];
// Rebuild indicates whether we have a pristine form (FALSE) or one that has been trough validation -> submission once.
if (empty($form_state['rebuild'])) {
if ($cid) {
// Attempt to load files for an existing comment.
$files = comment_upload_load_files($cid);
}
$form_state['storage']['comment_upload_files'] = $files;
}
else {
// Using the Attach button without JS does not generate a preview.
// Rape the comment form.
// Comment_form was not build to rebuild a form an fetch values from $form_state.
// To preserve changes made by the user, we build the part of the form we just received,
// mapping $_POST values to #default_values.
// Because rebuild is TRUE, $_POST has been validated. Nevertheless this is insane.
$form['#post'] = $_POST;
unset($form['#post']['op']);
$build_form = form_builder('comment_form', $form, $state);
comment_upload_value_to_default($build_form, $form);
if (isset($form_state['values']['files'])) {
$files = $form_state['values']['files'];
}
}
$form['attachments'] = array(
'#type' => 'fieldset',
'#access' => user_access('upload files'), // TODO: Change perm.
'#title' => t('File attachments'),
'#collapsible' => TRUE,
'#collapsed' => count($files) == 0,
'#description' => t('Changes made to the attachments are not permanent until you save this post. The first "listed" file will be included in RSS feeds.'),
'#prefix' => '
',
'#suffix' => '
',
);
// Wrapper for fieldset contents (used by ahah.js).
$form['attachments']['wrapper'] = array(
'#prefix' => '',
'#suffix' => '
',
);
$form['attachments']['wrapper'] += comment_upload_upload_form($files);
$form['#attributes']['enctype'] = 'multipart/form-data';
$form['store_file_information'] = array(
'#type' => 'value',
'#value' => $files,
);
// Comment_form dynamically adds buttons such as preview / save in the
// formbuilder. As the attachment fieldset contains an AHAH element, the
// #cache property of the form will be set to TRUE and the formbuilder will
// no longer be called. Therefore, comment_upload needs to implement preview
// functionality as well.
$form['preview'] = array(
'#type' => 'submit',
'#value' => t('Preview'),
'#submit' => array('comment_upload_comment_form_intermittent_submit'),
'#weight' => 20,
);
$form['#submit'] = array('comment_upload_comment_form_submit', 'comment_form_submit');
}
/*
* Listen to node deletion and delete files from comments on that node.
*
* (comment_nodeapi does not call comment APIs when deleting).
*
* Implementation of hook_nodeapi().
*/
function comment_upload_nodeapi(&$node, $op, $arg = 0) {
if ($op == 'delete') {
$result = db_query("SELECT fid, nid, filepath FROM {comment_upload} cu INNER JOIN {files} f ON cu.fid = f.fid WHERE cu.nid = %d", $node->nid);
while ($row = db_fetch_array($result)) {
// comment_upload_delete_file just needs a filepath and a fid.
comment_upload_delete_file($row);
}
}
}
/**
* Traverse a build form and #value to corresponding #default_values in an unbuild form.
*
* @param array $build_form
* @param array $form
*/
function comment_upload_value_to_default($build_form, &$form) {
if (!is_array($build_form)) {
return;
}
foreach (element_children($build_form) as $key) {
if (isset($form[$key]) && isset($build_form[$key]['#value'])) {
$form[$key]['#default_value'] = $build_form[$key]['#value'];
}
comment_upload_value_to_default($build_form[$key], $form[$key]);
}
}
/**
* Build the section of the upload_form that holds the attachments table and upload element.
*
* @param array $files
* @return array
*/
function comment_upload_upload_form($files = array()) {
$form = array('#theme' => 'comment_upload_form_new');
if (!empty($files) && is_array($files)) {
$count = count($files);
$form['files']['#theme'] = 'comment_upload_form_current';
$form['files']['#tree'] = TRUE;
foreach ($files as $key => $file) {
$file = (object) $file;
$form['files'][$key] = comment_upload_create_file_element($file, $count);
}
}
$limits = _upload_file_limits($GLOBALS['user']);
$form['new']['upload'] = array(
'#type' => 'file',
'#title' => t('Attach new file'),
'#size' => 40,
'#description' => ($limits['resolution'] ? t('Images are larger than %resolution will be resized. ', array('%resolution' => $limits['resolution'])) : '') . t('The maximum upload size is %filesize. Only files with the following extensions may be uploaded: %extensions. ', array('%extensions' => $limits['extensions'], '%filesize' => format_size($limits['file_size']))),
);
$form['new']['attach'] = array(
'#type' => 'submit',
'#value' => t('Attach'),
'#name' => 'attach',
'#ahah' => array(
'path' => 'comment-upload/js',
'wrapper' => 'attach-wrapper',
'progress' => array('type' => 'bar', 'message' => t('Please wait...')),
),
'#submit' => array('comment_upload_comment_form_attach_submit'),
);
return $form;
}
/**
* Create the form elements for one file in the attachments table.
*
* @param object $file
* @param integer $count
* @return array
*/
function comment_upload_create_file_element($file, $count) {
$element = array();
$description = "". check_plain(file_create_url($file->filepath)) ."";
$element['description'] = array('#type' => 'textfield', '#default_value' => !empty($file->description) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description );
$element['size'] = array('#value' => format_size($file->filesize));
$element['remove'] = array('#type' => 'checkbox', '#default_value' => !empty($file->remove));
$element['list'] = array('#type' => 'checkbox', '#default_value' => $file->list);
$element['weight'] = array('#type' => 'weight', '#delta' => $count, '#default_value' => $file->weight);
$element['filename'] = array('#type' => 'value', '#value' => $file->filename);
$element['filepath'] = array('#type' => 'value', '#value' => $file->filepath);
$element['filemime'] = array('#type' => 'value', '#value' => $file->filemime);
$element['filesize'] = array('#type' => 'value', '#value' => $file->filesize);
$element['fid'] = array('#type' => 'value', '#value' => $file->fid);
$element['new'] = array('#type' => 'value', '#value' => FALSE);
return $element;
}
/**
* Implementation of hook_comment().
*/
function comment_upload_comment(&$a1, $op) {
switch ($op) {
case 'insert':
case 'update':
comment_upload_save($a1);
break;
case 'delete':
comment_upload_comment_delete($a1);
break;
case 'view':
comment_upload_comment_view($a1);
break;
}
}
/**
* Remove files when the comment is deleted.
*
* @param object $comment
*/
function comment_upload_comment_delete($comment) {
$files = comment_upload_load_files($comment->cid);
if (!empty($files)) {
foreach ($files as $fid => $file) {
comment_upload_delete_file($file);
}
}
}
/**
* Add attachments to the comment on view or preview.
*
* @param object $comment
*/
function comment_upload_comment_view(&$comment) {
if (isset($comment->files)) {
$files = $comment->files;
}
else {
$files = comment_upload_load_files($comment->cid);
}
if (isset($files) && count($files)) {
$comment->comment .= theme('comment_upload_attachments', $files);
}
}
/**
* Save the changes made to attachments.
*
* @param array $comment
*/
function comment_upload_save($comment) {
if (!user_access('upload files to comments')) {
return;
}
if (!isset($comment['files']) || !is_array($comment['files'])) {
return;
}
foreach ($comment['files'] as $fid => $file) {
if (!empty($file['remove'])) {
comment_upload_delete_file($file);
}
// Later: make this nicer: add a 'new' flag for new files.
if (!empty($file['new'])) {
db_query("INSERT INTO {comment_upload} (fid, cid, nid, list, description, weight) VALUES (%d, %d, %d, %d, '%s', %d)", $fid, $comment['cid'], $comment['nid'], $file['list'], $file['description'], $file['weight']);
$file = (object)$file;
file_set_status($file, FILE_STATUS_PERMANENT);
}
else {
db_query("UPDATE {comment_upload} SET list = %d, description = '%s', weight = %d WHERE fid = %d", $file['list'], $file['description'], $file['weight'], $fid);
}
}
}
/**
* React to the Attach button; update file information on AHAH request.
*
*/
function comment_upload_js() {
$cached_form_state = array();
if (!$stored_form = form_get_cache($_POST['form_build_id'], $cached_form_state)) {
exit();
}
$form_state = array('values' => $_POST, 'storage' => $cached_form_state['storage']);
comment_upload_process_files($stored_form, $form_state);
foreach ($form_state['values']['files'] as $fid => $file) {
$files[$fid] = $form_state['storage']['comment_upload_files'][$fid];
}
$upload_form = comment_upload_upload_form($files);
$stored_form['attachments']['wrapper'] = array_merge($stored_form['attachments']['wrapper'], $upload_form);
$cached_form_state['storage']['comment_upload_files'] = $form_state['storage']['comment_upload_files'];
form_set_cache($_POST['form_build_id'], $stored_form, $cached_form_state);
foreach ($files as $fid => $file) {
if (is_numeric($fid)) {
$upload_form['files'][$fid]['description']['#default_value'] = $form_state['values']['files'][$fid]['description'];
$upload_form['files'][$fid]['list']['#default_value'] = isset($form_state['values']['files'][$fid]['list']) ? 1 : 0;
$upload_form['files'][$fid]['remove']['#default_value'] = isset($form_state['values']['files'][$fid]['remove']) ? 1 : 0;
$upload_form['files'][$fid]['weight']['#default_value'] = $form_state['values']['files'][$fid]['weight'];
}
}
// Render the form for output.
$upload_form += array(
'#post' => $_POST,
'#programmed' => FALSE,
'#tree' => FALSE,
'#parents' => array(),
);
drupal_alter('form', $upload_form, array(), 'comment_upload_js');
$form_state = array('submitted' => FALSE);
$build_form = form_builder('comment_upload_js', $upload_form, $form_state);
$output = theme('status_messages') . drupal_render($build_form);
// We send the updated file attachments form.
// Don't call drupal_json(). ahah.js uses an iframe and
// the header output by drupal_json() causes problems in some browsers.
print drupal_to_js(array('status' => TRUE, 'data' => $output));
exit();
}
/**
* Process file uploads.
*
* @param array $form
* @param array $form_state
*/
function comment_upload_process_files($form, &$form_state) {
$limits = _upload_file_limits($GLOBALS['user']);
$validators = array(
'file_validate_extensions' => array($limits['extensions']),
'file_validate_image_resolution' => array($limits['resolution']),
'file_validate_size' => array($limits['file_size'], $limits['user_size']),
);
// Save new file uploads.
if (user_access('upload files to comments') && ($file = file_save_upload('upload', $validators, file_directory_path()))) {
$file->list = variable_get('upload_list_default', 1);
$file->description = $file->filename;
$file->weight = 0;
if (!isset($form_state['values']['files'][$file->fid]['filepath'])) {
$form_state['values']['files'][$file->fid] = (array)$file;
$file->new = TRUE;
$form_state['storage']['comment_upload_files'][$file->fid] = (array) $file;
}
}
else {
// If no file has been entered, we have an upload element in files.
unset($form_state['values']['files']['upload']);
}
if (isset($form_state['values']['files'])) {
foreach ($form_state['values']['files'] as $fid => $file) {
$form_state['values']['files'][$fid]['new'] = !empty($form_state['storage']['comment_upload_files'][$fid]['new']) ? TRUE : FALSE;
}
}
// Order the form according to the set file weight values.
if (!empty($form_state['values']['files'])) {
$microweight = 0.001;
foreach ($form_state['values']['files'] as $fid => $file) {
if (is_numeric($fid)) {
$form_state['values']['files'][$fid]['#weight'] = $file['weight'] + $microweight;
$microweight += 0.001;
}
}
uasort($form_state['values']['files'], 'element_sort');
}
}
/**
* Additional submit handler for the comment form.
*
* @param array $form
* @param array $form_state
*/
function comment_upload_comment_form_submit($form, &$form_state) {
comment_upload_process_files($form, $form_state);
unset($form_state['storage']);
}
/**
* Submit handler for the preview and attach button.
*
* @param array $form
* @param array $form_state
*/
function comment_upload_comment_form_intermittent_submit($form, &$form_state) {
comment_upload_process_files($form, $form_state);
$form_state['rebuild'] = TRUE;
return;
}
/**
* Theme the attachments list.
*
* Taken from upload.module.
*
* @ingroup themeable
*/
function theme_comment_upload_form_current(&$form) {
$header = array('', t('Delete'), t('List'), t('Description'), t('Weight'), t('Size'));
drupal_add_tabledrag('comment-upload-attachments', 'order', 'sibling', 'comment-upload-weight');
foreach (element_children($form) as $key) {
// Add class to group weight fields for drag and drop.
$form[$key]['weight']['#attributes']['class'] = 'comment-upload-weight';
$row = array('');
$row[] = drupal_render($form[$key]['remove']);
$row[] = drupal_render($form[$key]['list']);
$row[] = drupal_render($form[$key]['description']);
$row[] = drupal_render($form[$key]['weight']);
$row[] = drupal_render($form[$key]['size']);
$rows[] = array('data' => $row, 'class' => 'draggable');
}
$output = theme('table', $header, $rows, array('id' => 'comment-upload-attachments'));
$output .= drupal_render($form);
return $output;
}
function theme_comment_upload_form_new($form) {
drupal_add_tabledrag('comment=upload-attachments', 'order', 'sibling', 'comment-upload-weight');
$output = drupal_render($form);
return $output;
}
function theme_comment_upload_attachments($files) {
$header = array(t('Attachment'), t('Size'));
$rows = array();
foreach ($files as $file) {
$file = (object)$file;
if ($file->list && empty($file->remove)) {
$href = file_create_url($file->filepath);
$text = $file->description ? $file->description : $file->filename;
$rows[] = array(l($text, $href), format_size($file->filesize));
}
}
if (count($rows)) {
return theme('table', $header, $rows, array('id' => 'attachments'));
}
}
/**
* Load attachments that belong to the comment.
*
* @param integer $cid
* @return array
*/
function comment_upload_load_files($cid) {
$files = array();
$result = db_query('SELECT * FROM {files} f INNER JOIN {comment_upload} cu ON f.fid = cu.fid WHERE cu.cid = %d ORDER BY cu.weight, f.fid', $cid);
while ($file = db_fetch_array($result)) {
$files[$file['fid']] = $file;
}
return $files;
}
/**
* Delete a file and its associated records.
*
* @param array $file
*/
function comment_upload_delete_file($file) {
file_delete($file['filepath']);
db_query('DELETE FROM {files} WHERE fid = %d', $file['fid']);
db_query("DELETE FROM {comment_upload} WHERE fid = %d", $file['fid']);
}