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', 'view files uploaded to comments'); } function comment_upload_theme() { return array( 'comment_upload_attachments' => array( 'template' => 'comment-upload-attachments', 'arguments' => array('files' => NULL, 'display_images' => FALSE, 'preset' => 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, 0), '#options' => array(t('Disabled'), t('Enabled')), '#weight' => 20, ); $image_options = array( 'none' => t('Display as attachment'), 'inline' => t('Display as full image'), ); if (module_exists('imagecache')) { $presets = imagecache_presets(); foreach ($presets as $preset_id => $preset) { $image_options[$preset_id] = t('Display via imagecache preset !preset', array('!preset' => $preset['presetname'])); } } $form['comment']['comment_upload_images'] = array( '#type' => 'select', '#title' => t('Image attachments on comments'), '#default_value' => variable_get('comment_upload_images_'. $form['#node_type']->type, 'none'), '#options' => $image_options, '#weight' => 20, ); } elseif ($form_id == 'comment_form') { comment_upload_alter_comment_form($form, $form_state); } } /** * Alter the comment form to support AHAH uploads. * * Note; not a hook implementation. */ function comment_upload_alter_comment_form(&$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); } } 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 and 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. $copy_form = $form; $copy_form['#post'] = $_POST; unset($copy_form['#post']['op']); unset($copy_form['#after_build']); $build_form = form_builder('comment_form', $copy_form, $state); comment_upload_value_to_default($build_form, $form); form_clean_id(NULL, TRUE); if (isset($form_state['storage']['comment_upload_storage'])) { $files = $form_state['storage']['comment_upload_storage']; if (count($files) > 1) { uasort($files, 'comment_upload_sort_files'); } } } $form['#comment_upload_storage'] = $files; $form['attachments'] = array( '#type' => 'fieldset', '#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.'), '#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'; // 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['#validate'][] = 'comment_upload_comment_form_validate'; } /** * Implementation of hook_file_download(). */ function comment_upload_file_download($filepath) { $filepath = file_create_path($filepath); $result = db_query("SELECT f.*, cu.nid FROM {files} f INNER JOIN {comment_upload} cu ON f.fid = cu.fid WHERE filepath = '%s'", $filepath); if ($file = db_fetch_object($result)) { if (user_access('view files uploaded to comments') && ($node = node_load($file->nid)) && node_access('view', $node)) { return array( 'Content-Type: '. mime_header_encode($file->filemime), 'Content-Length: '. $file->filesize, ); } else { return -1; } } } /* * 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 cu.fid, cu.nid, f.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); } } } /** * 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; } } /** * Traverse a build form and copy #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'), '#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(), '#validate' => array('comment_upload_comment_form_validate'), ); 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; } /** * 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) { $display_images = FALSE; $preset_name = ''; if (isset($comment->files)) { $files = $comment->files; } else { $files = comment_upload_load_files($comment->cid); } if (isset($files) && count($files)) { if (user_access('view files uploaded to comments')) { $node = node_load($comment->nid); $display_setting = variable_get('comment_upload_images_'. $node->type, 'none'); if ($display_setting != 'none') { $display_images = TRUE; if (is_numeric($display_setting)) { $imagecache_preset = imagecache_preset($display_setting); $preset_name = $imagecache_preset['presetname']; } } $comment->files = $files; $comment->comment .= theme('comment_upload_attachments', $files, $display_images, $preset_name); } } } /** * 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); } 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(); $files = array(); // Load the form from the Form API cache. if (empty($_POST) || !($cached_form = form_get_cache($_POST['form_build_id'], $cached_form_state)) || !isset($cached_form['#comment_upload_storage']) || !isset($cached_form['attachments'])) { form_set_error('form_token', t('Validation error, please try again. The file you attempted to upload may be too large. If this error persists, please contact the site administrator.')); $output = theme('status_messages'); print drupal_to_js(array('status' => TRUE, 'data' => $output)); exit(); } $form_state = array('values' => $_POST); comment_upload_process_files($cached_form, $form_state); if (!empty($form_state['values']['files'])) { foreach ($form_state['values']['files'] as $fid => $file) { if (isset($cached_form['#comment_upload_storage'][$fid])) { $files[$fid] = $cached_form['#comment_upload_storage'][$fid]; } } } $form = comment_upload_upload_form($files); unset($cached_form['attachments']['wrapper']['new']); $cached_form['attachments']['wrapper'] = array_merge($cached_form['attachments']['wrapper'], $form); $cached_form['attachments']['#collapsed'] = FALSE; form_set_cache($_POST['form_build_id'], $cached_form, $cached_form_state); foreach ($files as $fid => $file) { if (is_numeric($fid)) { $form['files'][$fid]['description']['#default_value'] = $form_state['values']['files'][$fid]['description']; $form['files'][$fid]['list']['#default_value'] = !empty($form_state['values']['files'][$fid]['list']); $form['files'][$fid]['remove']['#default_value'] = !empty($form_state['values']['files'][$fid]['remove']); $form['files'][$fid]['weight']['#default_value'] = $form_state['values']['files'][$fid]['weight']; } } // Render the form for output. $form += array( '#post' => $_POST, '#programmed' => FALSE, '#tree' => FALSE, '#parents' => array(), ); drupal_alter('form', $form, array(), 'comment_upload_js'); $form_state = array('submitted' => FALSE); $form = form_builder('comment_upload_js', $form, $form_state); $output = theme('status_messages') . drupal_render($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. $GLOBALS['devel_shutdown'] = FALSE; echo 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; $file->remove = 0; if (!isset($form_state['values']['files'][$file->fid]['filepath'])) { $form_state['values']['files'][$file->fid] = (array) $file; $file->new = TRUE; $form['#comment_upload_storage'][$file->fid] = (array) $file; } } else { $errors = form_get_errors(); if (!empty($errors)) { return 0; } else { // If no file has been entered, we have an empty 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['#comment_upload_storage'][$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_validate($form, &$form_state) { if (empty($form_state['values']['op']) && (isset($form_state['clicked_button']) && $form_state['clicked_button']['#value'] != t('Attach'))) { return; } elseif(!empty($form_state['rebuild'])) { return; } comment_upload_process_files($form, $form_state); foreach ($form['#comment_upload_storage'] as $fid => $file) { if (is_numeric($fid)) { $form['#comment_upload_storage'][$fid]['description'] = $form_state['values']['files'][$fid]['description']; $form['#comment_upload_storage'][$fid]['list'] = $form_state['values']['files'][$fid]['list']; $form['#comment_upload_storage'][$fid]['remove'] = $form_state['values']['files'][$fid]['remove']; $form['#comment_upload_storage'][$fid]['weight'] = $form_state['values']['files'][$fid]['weight']; $form_state['storage']['comment_upload_storage'][$fid] = $form['#comment_upload_storage'][$fid]; } } if ((isset($form_state['values']['op']) && $form_state['values']['op'] == t('Preview')) || (isset($form_state['clicked_button']) && $form_state['clicked_button']['#value'] == t('Attach'))) { $form_state['rebuild'] = TRUE; } elseif (!empty($form_state['submitted'])) { unset($form_state['storage']); } return; } /** * 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']); } /** * Sort on weight, if weights are equal compare fid. * * uasort callback. */ function comment_upload_sort_files($a, $b) { if ($a['weight'] == $b['weight']) { return ($a['fid'] < $b['fid']) ? -1 : 1; } return ($a['weight'] < $b['weight']) ? -1 : 1; } /** * Theme the attachments list in the upload form. * * 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; } /** * Theme the upload a new attachment part of the upload form. * * Taken from upload.module. * * @ingroup themeable */ 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 template_preprocess_comment_upload_attachments(&$variables) { $row = 0; $variables['images'] = array(); $variables['attachments'] = array(); foreach ($variables['files'] as $fid => $file) { if ($file['list'] && empty($file['remove'])) { $text = $file['description'] ? $file['description'] : $file['filename']; if ($variables['display_images'] && strpos($file['filemime'], 'image/') === 0) { if ($variables['preset']) { $variables['images'][$fid]['image'] = theme('imagecache', $variables['preset'], file_create_path($file['filepath']), $text, $text); } else { $variables['images'][$fid]['image'] = theme('image', file_create_path($file['filepath']), $text, $text); } $variables['images'][$fid]['url'] = check_url(file_create_url($file['filepath'])); } else { $variables['attachments'][$fid]['zebra'] = $row % 2 == 0 ? 'odd' : 'even'; $variables['attachments'][$fid]['text'] = check_plain($text); $variables['attachments'][$fid]['url'] = check_url(file_create_url($file['filepath'])); $variables['attachments'][$fid]['size'] = format_size($file['filesize']); $row++; } } } }