Parent Directory
|
Revision Log
|
Revision Graph
First release of Node Media includes the following features: - Upload mpeg videos - Upload sounds(only upload) - Limit number of uploads according to type: image, sound, video - Some minor bug fixing
| 1 | <?php |
| 2 | /** |
| 3 | * @file |
| 4 | * This module will add "Images" tab, "Videos" tab and "Sounds" tab to nodes, allowing users to upload media(images, video and sounds) and associate them with the current node. |
| 5 | * Requires upload.module |
| 6 | * The hook_node_media() allows to conditionally disable media for the specific node |
| 7 | */ |
| 8 | |
| 9 | /** |
| 10 | * Implementation of hook_perm(). |
| 11 | */ |
| 12 | |
| 13 | function node_media_perm() { |
| 14 | return array('create node media', 'administer node media'); |
| 15 | } |
| 16 | |
| 17 | /** |
| 18 | * Implementation of hook_menu(). |
| 19 | */ |
| 20 | function node_media_menu($may_cache) { |
| 21 | $items = array(); |
| 22 | $access = user_access('create node media'); |
| 23 | $admin = user_access('administer site configuration'); |
| 24 | |
| 25 | if ($may_cache) { |
| 26 | $items[] = array('path' => 'admin/settings/node_media', |
| 27 | 'title' => t('node media'), |
| 28 | 'description' => t('Control how to upload node media.'), |
| 29 | 'callback' => 'drupal_get_form', |
| 30 | 'callback arguments' => array('node_media_admin_settings'), |
| 31 | 'access' => $admin |
| 32 | ); |
| 33 | $items[] = array( |
| 34 | 'path' => 'node_media/js', |
| 35 | 'callback' => '_node_media_js', |
| 36 | 'access' => $access, |
| 37 | 'type' => MENU_CALLBACK |
| 38 | ); |
| 39 | } else { |
| 40 | if (arg(0) == 'node' && is_numeric(arg(1))) { |
| 41 | $node = node_load(arg(1)); |
| 42 | if ($node->nid && variable_get('node_media_position_'.$node->type, 'hide') != 'hide') { |
| 43 | $items[] = array( |
| 44 | 'path' => 'node/' . arg(1) . '/media', |
| 45 | 'title' => t('Media'), |
| 46 | 'callback' => '_node_media_edit_form', |
| 47 | 'callback arguments' => array($node), |
| 48 | 'access' => $access && node_access('update', $node), |
| 49 | 'type' => MENU_LOCAL_TASK, |
| 50 | 'weight' => 2 |
| 51 | ); |
| 52 | $items[] = array( |
| 53 | 'path' => 'node/' . arg(1) . '/image_gallery', |
| 54 | 'title' => t('Image gallery'), |
| 55 | 'callback' => '_node_media_image_gallery', |
| 56 | 'callback arguments' => array($node), |
| 57 | 'access' => count($node->node_media[0]) && node_access('view', $node), |
| 58 | 'type' => variable_get('node_media_gallery_tabs_'.$node->type, TRUE) ? MENU_LOCAL_TASK : MENU_CALLBACK |
| 59 | ); |
| 60 | $items[] = array( |
| 61 | 'path' => 'node/' . arg(1) . '/video_gallery', |
| 62 | 'title' => t('Video gallery'), |
| 63 | 'callback' => '_node_media_video_gallery', |
| 64 | 'callback arguments' => array($node), |
| 65 | 'access' => count($node->node_media[1]) && node_access('view', $node), |
| 66 | 'type' => variable_get('node_media_gallery_tabs_'.$node->type, TRUE) ? MENU_LOCAL_TASK : MENU_CALLBACK |
| 67 | ); |
| 68 | $items[] = array( |
| 69 | 'path' => 'node/' . arg(1) . '/sound_gallery', |
| 70 | 'title' => t('Sound gallery'), |
| 71 | 'callback' => '_node_media_sound_gallery', |
| 72 | 'callback arguments' => array($node), |
| 73 | 'access' => count($node->node_media[2]) && node_access('view', $node), |
| 74 | 'type' => variable_get('node_media_gallery_tabs_'.$node->type, TRUE) ? MENU_LOCAL_TASK : MENU_CALLBACK |
| 75 | ); |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | return $items; |
| 81 | } |
| 82 | |
| 83 | /** |
| 84 | * implementation of hook_form_alter() |
| 85 | */ |
| 86 | function node_media_form_alter($form_id, &$form) { |
| 87 | $type = $form['#node_type']->type; |
| 88 | if ($form_id == 'node_type_form' && isset($form['identity']['type'])) { |
| 89 | // radio button in the node's content type configuration page |
| 90 | _node_media_check_settings(); |
| 91 | $form['node_media'] = array( |
| 92 | '#type' => 'fieldset', |
| 93 | '#title' => t('node media'), |
| 94 | '#collapsible' => TRUE, |
| 95 | ); |
| 96 | $form['node_media']['node_media_position'] = array( |
| 97 | '#type' => 'radios', |
| 98 | '#title' => t('Position'), |
| 99 | '#default_value' => variable_get('node_media_position_'.$type, 'hide'), |
| 100 | '#options' => array('hide' => t('Do not show'), 'above' => t('Show above node body'), 'below' => t('Show below node body'), |
| 101 | 'node_template' => t('Manually set in node template by variable $node->node_media')), |
| 102 | '#description' => t('The position of images in the node view.'), |
| 103 | ); |
| 104 | $form['node_media']['node_media_gallery_link'] = array( |
| 105 | '#type' => 'radios', |
| 106 | '#title' => t('Link to image gallery'), |
| 107 | '#default_value' => variable_get('node_media_gallery_link_'.$type, TRUE), |
| 108 | '#options' => array(t('Do not show'), t('Show')), |
| 109 | '#description' => t('Choose whether to show or not the link to the image gallery.'), |
| 110 | ); |
| 111 | $form['node_media']['node_media_gallery_tabs'] = array( |
| 112 | '#type' => 'radios', |
| 113 | '#title' => t('Media galleries tabs'), |
| 114 | '#default_value' => variable_get('node_media_gallery_tabs_'.$type, TRUE), |
| 115 | '#options' => array(t('Do not show'), t('Show')), |
| 116 | '#description' => t('Choose whether to show or not the tabs to the media galleries.'), |
| 117 | ); |
| 118 | $form['node_media']['node_media_gallery_display_node_details'] = array( |
| 119 | '#type' => 'radios', |
| 120 | '#title' => t('Node details in media galleries'), |
| 121 | '#default_value' => variable_get('node_media_gallery_display_node_details_'.$type, TRUE), |
| 122 | '#options' => array(t('Do not show'), t('Show')), |
| 123 | '#description' => t('Choose whether to show or not the node details into media galleries.'), |
| 124 | ); |
| 125 | $form['node_media']['node_media_teaser_images'] = array( |
| 126 | '#type' => 'textfield', |
| 127 | '#title' => t('Number of images in node teaser'), |
| 128 | '#default_value' => variable_get('node_media_teaser_images_'.$type, 2), |
| 129 | '#size' => 5, |
| 130 | '#maxlength' => 2, |
| 131 | '#description' => t('The maximum number of images to show in the node teaser. 0 will not show images. Leave blank to show all images.'), |
| 132 | ); |
| 133 | $form['node_media']['node_media_body_images'] = array( |
| 134 | '#type' => 'textfield', |
| 135 | '#title' => t('Number of images in node body'), |
| 136 | '#size' => 5, |
| 137 | '#maxlength' => 2, |
| 138 | '#default_value' => variable_get('node_media_body_images_'.$type, NULL), |
| 139 | '#description' => t('The maximum number of images to show in the node body. 0 will not show images. Leave blank to show all images.'), |
| 140 | ); |
| 141 | $form['node_media']['node_media_teaser_format'] = array( |
| 142 | '#type' => 'radios', |
| 143 | '#title' => t('Image format in node teaser'), |
| 144 | '#default_value' => variable_get('node_media_teaser_format_'.$type, 'thumbs'), |
| 145 | '#options' => array('thumbs' => t('Thumbnails'), 'fullsize' => t('Full size images')), |
| 146 | '#description' => t('Image format in node teaser.'), |
| 147 | ); |
| 148 | $form['node_media']['node_media_body_format'] = array( |
| 149 | '#type' => 'radios', |
| 150 | '#title' => t('Image format in node body'), |
| 151 | '#default_value' => variable_get('node_media_body_format_'.$type, 'thumbs'), |
| 152 | '#options' => array('thumbs' => t('Thumbnails'), 'fullsize' => t('Full size images')), |
| 153 | '#description' => t('Image format in node body.'), |
| 154 | ); |
| 155 | |
| 156 | $form['submit']['#weight'] = 30; |
| 157 | $form['delete']['#weight'] = 31; |
| 158 | $form['reset']['#weight'] = 32; |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * Implementation of hook_nodeapi(). |
| 164 | */ |
| 165 | function node_media_nodeapi(&$node, $op, $teaser, $page) { |
| 166 | // test if images are allowed for this node |
| 167 | if ($op == 'load' || $op == 'view') { |
| 168 | if (variable_get('node_media_position_'.$node->type, 'hide') == 'hide') return; |
| 169 | |
| 170 | // fire an additional hook for the specific node type |
| 171 | // i.e. node media might be a pro feature, so only subscribed users can view them |
| 172 | // if the hook is not present in the node type module, images are loaded by default |
| 173 | $show = module_invoke($node->type, 'node_media', $op, $node); |
| 174 | if ($show === FALSE) return; |
| 175 | } |
| 176 | |
| 177 | switch ($op) { |
| 178 | case 'load': |
| 179 | // load node media for the current node |
| 180 | $sql = db_query('SELECT * FROM {node_media} WHERE nid=%d AND status=1 ORDER BY type,weight', $node->nid); |
| 181 | while ($r = db_fetch_object($sql)) { |
| 182 | $node->node_media[$r->type][$r->id] = $r; |
| 183 | } |
| 184 | break; |
| 185 | |
| 186 | case 'insert': |
| 187 | if (user_access('create node media') && node_access('update', $node) && |
| 188 | variable_get('node_media_position_'.$node->type, 'hide') != 'hide') { |
| 189 | drupal_set_message(t('Click the <strong>media</strong> tab to add images, videos and sounds to this node.', |
| 190 | array( |
| 191 | '!images' => l(t('images'), 'node/'.$node->nid.'/images')/*, |
| 192 | '!videos' => l(t('videos'), 'node/'.$node->nid.'/videos'), |
| 193 | '!sounds' => l(t('sounds'), 'node/'.$node->nid.'/sounds')*/ |
| 194 | ))); |
| 195 | } |
| 196 | break; |
| 197 | |
| 198 | case 'delete': |
| 199 | // Delete image and thumbnail files |
| 200 | $sql = db_query('SELECT filepath, thumbpath FROM {node_media} WHERE nid=%d', $node->nid); |
| 201 | while ($r = db_fetch_object($sql)) { |
| 202 | file_delete($r->filepath); |
| 203 | file_delete($r->thumbpath); |
| 204 | } |
| 205 | // Delete all images associated with the node |
| 206 | db_query('DELETE FROM {node_media} WHERE nid=%d', $node->nid); |
| 207 | break; |
| 208 | |
| 209 | case 'view': |
| 210 | if (empty($node->node_media)) return; |
| 211 | |
| 212 | // search for a themed view for the current node type |
| 213 | $nodetype_function = 'theme_'.$node->type.'_node_media_view'; |
| 214 | if (function_exists($nodetype_function)) { |
| 215 | $output = $nodetype_function($node, $teaser, $page); |
| 216 | } |
| 217 | else { |
| 218 | // use the default theme function |
| 219 | $output = theme('node_media_view', $node, $teaser, $page); |
| 220 | } |
| 221 | $node->node_media = $output; |
| 222 | |
| 223 | $output = '<div class="node_media">'.$output[0].'</div>'; |
| 224 | $position = variable_get('node_media_position_'.$node->type, 'hide'); |
| 225 | switch ($position) { |
| 226 | case 'node_template': |
| 227 | case 'hide': |
| 228 | break; |
| 229 | |
| 230 | case 'above': |
| 231 | $node->content['node_media'] = array('#value' => $output, '#weight' => -1); |
| 232 | break; |
| 233 | |
| 234 | default: |
| 235 | $node->content['node_media'] = array('#value' => $output, '#weight' => 1); |
| 236 | break; |
| 237 | } |
| 238 | return $node; |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | /** |
| 243 | * Implementation of hook_link(). |
| 244 | */ |
| 245 | function node_media_link($type, $node = null, $teaser = false) { |
| 246 | $links = array(); |
| 247 | |
| 248 | if ($type == 'node' && $node->nid && variable_get('node_media_position_'.$node->type, 'hide') != 'hide') { |
| 249 | if ($node->node_media[0] && variable_get('node_media_gallery_link_'.$node->type, TRUE)) { |
| 250 | $links['node_media_image_gallery'] = array( |
| 251 | 'title' => t('Open the image gallery'), |
| 252 | 'href' => "node/$node->nid/image_gallery", |
| 253 | ); |
| 254 | } |
| 255 | |
| 256 | if ($node->node_media[1] && variable_get('node_media_gallery_link_'.$node->type, TRUE)) { |
| 257 | $links['node_media_video_gallery'] = array( |
| 258 | 'title' => t('Open the video gallery'), |
| 259 | 'href' => "node/$node->nid/video_gallery", |
| 260 | ); |
| 261 | } |
| 262 | |
| 263 | if ($node->node_media[2] && variable_get('node_media_gallery_link_'.$node->type, TRUE)) { |
| 264 | $links['node_media_sound_gallery'] = array( |
| 265 | 'title' => t('Open the sound gallery'), |
| 266 | 'href' => "node/$node->nid/sound_gallery", |
| 267 | ); |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | return $links; |
| 272 | } |
| 273 | |
| 274 | /** |
| 275 | * Implementation of hook_user(). |
| 276 | */ |
| 277 | function node_media_user($type, &$edit, &$user, $category = NULL) { |
| 278 | switch ($type) { |
| 279 | case 'delete': |
| 280 | // Set uid=0 for images uploaded by the deleted user |
| 281 | db_query('UPDATE {node_media} SET uid=0 WHERE uid=%d', $user->uid); |
| 282 | } |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * Implementation of hook_file_download(). |
| 287 | * Find out if an image is accessible when download method is set to private |
| 288 | */ |
| 289 | function node_media_file_download($file) { |
| 290 | $path = file_create_path($file); |
| 291 | $result = db_query("SELECT * FROM {node_media} WHERE filepath='%s' OR thumbpath='%s'", $path, $path); |
| 292 | if ($file = db_fetch_object($result)) { |
| 293 | $node = node_load($file->nid); |
| 294 | if (node_access('view', $node)) { |
| 295 | if ($path == $file->thumbpath) { |
| 296 | // update header info if thumb is requested |
| 297 | $name = mime_header_encode(basename($file->thumbpath)); |
| 298 | $size = $file->thumbsize; |
| 299 | } |
| 300 | else { |
| 301 | $name = mime_header_encode($file->filename); |
| 302 | $size = $file->filesize; |
| 303 | } |
| 304 | $type = mime_header_encode($file->filemime); |
| 305 | return array( |
| 306 | 'Content-Type: '. $type .'; name='. $name, |
| 307 | 'Content-Length: '. $size, |
| 308 | 'Cache-Control: private' |
| 309 | ); |
| 310 | } |
| 311 | else { |
| 312 | return -1; |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | |
| 318 | /************************************************************ |
| 319 | * Node edit functions |
| 320 | ************************************************************/ |
| 321 | |
| 322 | /** |
| 323 | * Display the page to edit and upload node media. |
| 324 | * |
| 325 | * @param $node |
| 326 | * the current node |
| 327 | * @return |
| 328 | * the node media page. |
| 329 | */ |
| 330 | function _node_media_edit_form($node) { |
| 331 | global $user; |
| 332 | $output = '<div id="attach-wrapper">'; |
| 333 | |
| 334 | // Generate image list |
| 335 | $sql = db_query('SELECT * FROM {node_media} WHERE nid=%d ORDER BY weight', $node->nid); |
| 336 | if (db_num_rows($sql)>0) $output .= drupal_get_form('_node_media_list', $node, $sql); |
| 337 | |
| 338 | // test if this node can accept new images |
| 339 | // i.e. there might be limits to the number of uploaded images for certain node types or user roles |
| 340 | // if the hook is not present in the node type module, upload is enabled by default |
| 341 | $upload = module_invoke($node->type, 'node_media', 'upload', $node); |
| 342 | if ($upload !== FALSE) { |
| 343 | $output .= drupal_get_form('_node_media_edit_form_upload', $node); |
| 344 | } |
| 345 | $output .= '</div>'; |
| 346 | |
| 347 | drupal_set_title(check_plain($node->title)); |
| 348 | return $output; |
| 349 | } |
| 350 | |
| 351 | function _node_media_edit_form_upload($node) { |
| 352 | $path = file_directory_path().'/'.variable_get('node_media_path', 'node_media'); |
| 353 | _node_media_check_directory(NULL, $path); |
| 354 | $extensions = variable_get('node_media_extensions', 'jpg jpeg gif png'); |
| 355 | |
| 356 | //die; |
| 357 | |
| 358 | drupal_add_js('misc/progress.js'); |
| 359 | drupal_add_js('misc/upload.js'); |
| 360 | |
| 361 | // Generate upload form |
| 362 | $form['new'] = array( |
| 363 | '#type'=>'fieldset', |
| 364 | '#title'=>t('Upload a new media'), |
| 365 | ); |
| 366 | $form['new']['description'] = array( |
| 367 | '#type' => 'textfield', |
| 368 | '#title' => t('Description'), |
| 369 | '#size' => 50, |
| 370 | '#maxlength' => 255, |
| 371 | '#default_value' => $edit['new']['description'], |
| 372 | '#description' => t('Enter a description for the new media, max 255 chars.'), |
| 373 | '#prefix' => '<div style="float:left; margin-right:20px;">', |
| 374 | '#suffix' => '</div>', |
| 375 | ); |
| 376 | $form['new']['weight'] = array( |
| 377 | '#type' => 'weight', |
| 378 | '#title' => t('Weight'), |
| 379 | '#prefix' => '<div style="float:left; margin-right:20px;">', |
| 380 | '#suffix' => '</div><div style="clear:both; height:0.1em;"></div>', |
| 381 | ); |
| 382 | $form['new']['upload'] = array( |
| 383 | '#type' => 'file', |
| 384 | '#title' => t('Media file'), |
| 385 | '#size' => 50, |
| 386 | '#description' => t('Allowed file types: %extensions', array('%extensions' => $extensions)), |
| 387 | '#prefix' => '<div style="float:left; margin-right:20px;"></div><div id="attach-hide" style="float:left; margin-right:20px;">', |
| 388 | '#suffix' => '</div>', |
| 389 | ); |
| 390 | $form['new']['type'] = array( |
| 391 | '#type' => 'select', |
| 392 | '#options' => array('Image','Video','Sound'), |
| 393 | '#title' => t('Type'), |
| 394 | '#default_value' => $edit['new']['type'], |
| 395 | '#prefix' => '<div style="float:left; margin-right:20px;">', |
| 396 | '#suffix' => '</div><div style="clear:both; height:0.1em;"></div>', |
| 397 | ); |
| 398 | $form['new']['submit'] = array( |
| 399 | '#type' => 'button', |
| 400 | '#value' => t('Upload'), |
| 401 | '#name'=> 'submit', |
| 402 | '#id' => 'attach-button', |
| 403 | ); |
| 404 | // The class "upload" triggers the js upload behaviour. |
| 405 | $form['attach-url'] = array( |
| 406 | '#type' => 'hidden', |
| 407 | '#value' => url('node_media/js', NULL, NULL, TRUE), |
| 408 | '#attributes' => array('class' => 'upload'), |
| 409 | ); |
| 410 | // Needed for JS |
| 411 | $form['nid'] = array( |
| 412 | '#type' => 'hidden', |
| 413 | '#value' => $node->nid, |
| 414 | ); |
| 415 | |
| 416 | $form['#attributes']['enctype'] = 'multipart/form-data'; |
| 417 | return $form; |
| 418 | } |
| 419 | |
| 420 | /** |
| 421 | * Generate a list of images associated to the current node. |
| 422 | * |
| 423 | * @param $node |
| 424 | * the current node |
| 425 | * @param $result |
| 426 | * SQL result containing the node media |
| 427 | * @return |
| 428 | * a form representing the image list. |
| 429 | */ |
| 430 | function _node_media_list($node, $result) { |
| 431 | global $user; |
| 432 | $images = array(); |
| 433 | $form['rows'] = array('#tree' => TRUE); |
| 434 | $delete_access = node_access('delete', $node); |
| 435 | |
| 436 | while ($image = db_fetch_object($result)) { |
| 437 | $images[$image->id] = $image; |
| 438 | $form['rows'][$image->id]['description'] = array( |
| 439 | '#type' => 'textfield', |
| 440 | '#default_value' => $image->description, |
| 441 | '#size' => 30, |
| 442 | '#maxlength' => 255, |
| 443 | ); |
| 444 | $form['rows'][$image->id]['weight'] = array( |
| 445 | '#type' => 'weight', |
| 446 | '#default_value' => $image->weight, |
| 447 | ); |
| 448 | |
| 449 | // images can be deleted only by users having delete access or users who uploaded the images |
| 450 | $disabled = (!$delete_access && $user->uid != $image->uid); |
| 451 | $form['rows'][$image->id]['delete'] = array('#type' => 'checkbox', '#disabled' => $disabled); |
| 452 | } |
| 453 | |
| 454 | $form['nid'] = array('#type' => 'value', '#value' => $node->nid); |
| 455 | $form['save'] = array( |
| 456 | '#type' => 'submit', |
| 457 | '#value' => t('Save changes'), |
| 458 | ); |
| 459 | $form['images'] = array( |
| 460 | '#type' => 'value', |
| 461 | '#value' => $images, |
| 462 | ); |
| 463 | $form['#theme'] = 'node_media_list'; |
| 464 | |
| 465 | // action must be set, otherwise it will be replaced by "node_media/js" (current $_GET['q'] on JS uploads) |
| 466 | $form['#action'] = url('node/'.$node->nid.'/media'); |
| 467 | |
| 468 | return $form; |
| 469 | } |
| 470 | |
| 471 | /** |
| 472 | * Theme node media list. |
| 473 | */ |
| 474 | function theme_node_media_list($form) { |
| 475 | $header = array(t('Thumbnail'), t('Description').' / '.t('Path'), t('Size'), t('Weight'), t('Delete')); |
| 476 | |
| 477 | $rows = array(); |
| 478 | foreach($form['images']['#value'] as $id=>$image) { |
| 479 | $row = array(); |
| 480 | $row[] = '<img src="'.file_create_url($image->thumbpath).'" vspace="5" />'; |
| 481 | $row[] = drupal_render($form['rows'][$image->id]['description']).$image->filepath; |
| 482 | $row[] = array('data' => format_size($image->filesize), 'style' => 'white-space: nowrap'); |
| 483 | $row[] = drupal_render($form['rows'][$image->id]['weight']); |
| 484 | $row[] = array('data' => drupal_render($form['rows'][$image->id]['delete']), 'align' => 'center'); |
| 485 | $rows[] = $row; |
| 486 | } |
| 487 | |
| 488 | $output = '<fieldset><legend>'.t('Uploaded images').'</legend>'; |
| 489 | $output .= theme('table', $header, $rows); |
| 490 | $output .= drupal_render($form); |
| 491 | $output .= '</fieldset>'; |
| 492 | |
| 493 | return $output; |
| 494 | } |
| 495 | |
| 496 | /** |
| 497 | * Process result from node media list. |
| 498 | */ |
| 499 | function _node_media_list_submit($form_id, $form_values) { |
| 500 | global $user; |
| 501 | $node = node_load($form_values['nid']); |
| 502 | if (!node_access('update', $node)) return; |
| 503 | $delete_access = node_access('delete', $node); |
| 504 | |
| 505 | foreach($form_values['rows'] as $id => $edit) { |
| 506 | if ($edit['delete']) { |
| 507 | $r = db_fetch_object(db_query('SELECT filepath, thumbpath, uid FROM {node_media} WHERE id=%d AND nid=%d', |
| 508 | $id, $node->nid)); |
| 509 | |
| 510 | // if user has no delete access to the node, he can delete his own images only |
| 511 | if (!$delete_access && $user->uid != $r->uid) continue; |
| 512 | |
| 513 | // delete selected image |
| 514 | file_delete($r->filepath); |
| 515 | file_delete($r->thumbpath); |
| 516 | db_query('DELETE FROM {node_media} WHERE id=%d AND nid=%d', $id, $node->nid); |
| 517 | } |
| 518 | else { |
| 519 | // update image data |
| 520 | db_query('UPDATE {node_media} SET description="%s", weight=%d WHERE id=%d AND nid=%d', |
| 521 | $edit['description'], $edit['weight'], $id, $node->nid); |
| 522 | } |
| 523 | } |
| 524 | drupal_set_message(t('The changes have been saved.')); |
| 525 | } |
| 526 | |
| 527 | /** |
| 528 | * Upload a new image and associate it to the current node. |
| 529 | * |
| 530 | * @param $edit |
| 531 | * array containing the submitted values |
| 532 | * @param $node |
| 533 | * the image file |
| 534 | * @return |
| 535 | * the uploaded image file, or FALSE if upload fails |
| 536 | */ |
| 537 | function _node_media_upload($edit, $node) { |
| 538 | global $user; |
| 539 | if (($file = file_check_upload()) && user_access('create node media')) { |
| 540 | switch($edit['type']){ |
| 541 | case 0: |
| 542 | // The uploaded file must be an image |
| 543 | $info = image_get_info($file->filepath); |
| 544 | if (!$info) { |
| 545 | form_set_error('upload', t('The uploaded file !name is not a valid image.', array('!name' => theme('placeholder', $file->filename)))); |
| 546 | return FALSE; |
| 547 | } |
| 548 | break; |
| 549 | case 1: |
| 550 | break; |
| 551 | case 2: |
| 552 | break; |
| 553 | } |
| 554 | |
| 555 | //Munge the filename as needed for security purposes. |
| 556 | $file->filename = upload_munge_filename($file->filename, NULL, 0); |
| 557 | |
| 558 | // Validate file extension |
| 559 | $extensions = variable_get('node_media_extensions', 'jpg jpeg gif png'); |
| 560 | $regex = '/\.('. ereg_replace(' +', '|', preg_quote($extensions)) .')$/i'; |
| 561 | if (!preg_match($regex, $file->filename)) { |
| 562 | form_set_error('upload', t('The selected file !name can not be uploaded, because it is only possible to attach files with the following extensions: !files-allowed.', array('!name' => theme('placeholder', $file->filename), '!files-allowed' => theme('placeholder', $extensions)))); |
| 563 | return FALSE; |
| 564 | } |
| 565 | switch($edit['type']){ |
| 566 | case 0: |
| 567 | // Validate max images for the current node |
| 568 | $sql = db_query('SELECT * FROM {node_media} WHERE nid=%d', $node->nid); |
| 569 | $max = variable_get('node_media_max_images', 4); |
| 570 | $count = db_num_rows($sql); |
| 571 | if ($count >= $max) { |
| 572 | form_set_error('upload', t('The selected file !name can not be uploaded, because it exceeds the maximum limit of !max files.', array('!name' => theme('placeholder', $file->filename), '!max' => theme('placeholder', $max))), 'error'); |
| 573 | return FALSE; |
| 574 | } |
| 575 | // Scale image to the maximum allowed resolution (default is 400x300) |
| 576 | // (if not set in the node_media admin settings, use the default upload max resolution |
| 577 | list($width, $height) = explode('x', variable_get('node_media_large_resolution', '400x300')); |
| 578 | if (!$width || !$height) { |
| 579 | list($width, $height) = explode('x', variable_get('upload_max_resolution', '400x300')); |
| 580 | } |
| 581 | if (!$width || !$height) { |
| 582 | list($width, $height) = explode('x', '400x300'); |
| 583 | } |
| 584 | if ($width && $height) { |
| 585 | $result = image_scale($file->filepath, $file->filepath, $width, $height); |
| 586 | if ($result) { |
| 587 | $file->filesize = filesize($file->filepath); |
| 588 | drupal_set_message(t('The image was resized to fit within the maximum allowed resolution of %resolution pixels.', |
| 589 | array('%resolution' => $width.'x'.$height))); |
| 590 | } |
| 591 | } |
| 592 | break; |
| 593 | case 1: |
| 594 | break; |
| 595 | case 2: |
| 596 | break; |
| 597 | } |
| 598 | // Validation from upload.module |
| 599 | $fid = 'upload_'.$user->uid; |
| 600 | $node->files = array($fid => $file); |
| 601 | _upload_validate($node); |
| 602 | if (!isset($node->files[$fid])) return FALSE; |
| 603 | |
| 604 | // Normalize filename and check description |
| 605 | $dest = _node_media_get_directory(); |
| 606 | if (trim($edit['description']) == '') { |
| 607 | $edit['description'] = basename($file->filename); |
| 608 | } |
| 609 | if (variable_get('node_media_md5name', FALSE)) { |
| 610 | // set md5 file name |
| 611 | $extension = substr($file->filename, strrpos($file->filename, '.') + 1); |
| 612 | $file->filename = substr(md5($dest.'/'.$file->filename), 0, 16); |
| 613 | if ($extension) $file->filename .= '.'.$extension; |
| 614 | } |
| 615 | $file->filename = preg_replace('/ +/', '_', $file->filename); |
| 616 | |
| 617 | // Save uploaded media |
| 618 | if ($file = file_save_upload($file, $dest.'/'.strtolower($file->filename))) { |
| 619 | $thumb = _node_media_create_thumbnail($file->filepath); |
| 620 | $file->filesize = filesize($file->filepath); |
| 621 | db_query("INSERT INTO {node_media} (nid, uid, filename, filepath, filemime, filesize, thumbpath, thumbsize, weight, description, type) |
| 622 | VALUES (%d, %d, '%s', '%s', '%s', %d, '%s', %d, %d, '%s', %d)", |
| 623 | $edit['nid'], $user->uid, $file->filename, $file->filepath, $file->filemime, $file->filesize, |
| 624 | $thumb->filepath, $thumb->filesize, $edit['weight'], $edit['description'], $edit['type']); |
| 625 | } |
| 626 | return $file; |
| 627 | } |
| 628 | } |
| 629 | |
| 630 | |
| 631 | /************************************************************ |
| 632 | * Node view functions |
| 633 | ************************************************************/ |
| 634 | |
| 635 | /** |
| 636 | * Show node media in the node view. |
| 637 | */ |
| 638 | function theme_node_media_view($node, $teaser, $page, $format = NULL) { |
| 639 | //if (str_replace(arg(2)) == 'image_gallery' || empty($node->node_media)) return; |
| 640 | |
| 641 | $output = array('','',''); |
| 642 | $i = 0; |
| 643 | |
| 644 | // set maximum number of images for teaser/body |
| 645 | $view = ($teaser ? 'teaser' : 'body'); |
| 646 | $count = variable_get('node_media_'.$view.'_images_'.$node->type, 2); |
| 647 | if (isset($count) && $count === 0) return; |
| 648 | if (!$format) { |
| 649 | $format = variable_get('node_media_'.$view.'_format_'.$node->type, 'thumbs'); |
| 650 | } |
| 651 | |
| 652 | foreach((array)$node->node_media[0] as $id=>$image) { |
| 653 | $description = check_plain($image->description); |
| 654 | $pattern = '<img src="%path" alt="%description" />'; |
| 655 | $thumb = strtr($pattern, array('%path'=>file_create_url($image->thumbpath), '%description'=>$description)); |
| 656 | $fullsize = strtr($pattern, array('%path'=>file_create_url($image->filepath), '%description'=>$description)); |
| 657 | |
| 658 | if ($info = getimagesize($image->filepath)) { |
| 659 | $width = $info[0] + 36; |
| 660 | $height = $info[1] + 36; |
| 661 | } |
| 662 | else { |
| 663 | $width = 420; |
| 664 | $height = 315; |
| 665 | } |
| 666 | |
| 667 | if ($format == 'thumbs') { |
| 668 | $output[0] .= '<a href="javascript:void(0);" title="'.$description.'" onclick="window.open(\''. |
| 669 | file_create_url($image->filepath).'\', \'\', \'height='.$height.',width='.$width.'\');">'.$thumb.'</a> '; |
| 670 | } |
| 671 | else { |
| 672 | $output[0] .= $fullsize.' '; |
| 673 | } |
| 674 | if ($count>0 && ++$i >= $count) break; |
| 675 | } |
| 676 | |
| 677 | foreach((array)$node->node_media[1] as $id=>$video) { |
| 678 | $description = check_plain($video->description); |
| 679 | $iconpath = variable_get('video_media_icon_path','/modules/node_media/img/video_icon.gif'); |
| 680 | $pattern = '<img src="'.$iconpath.'" alt="%description" />'; |
| 681 | $thumb = strtr($pattern, array('%path'=>file_create_url($iconpath), '%description'=>$description)); |
| 682 | $fullsize = strtr($pattern, array('%path'=>file_create_url($iconpath), '%description'=>$description)); |
| 683 | |
| 684 | list($width, $height) = explode('x', variable_get('video_media_max_resolution', '400x300')); |
| 685 | |
| 686 | if ($format == 'thumbs') { |
| 687 | $output[1] .= '<a href="javascript:void(0);" title="'.$description.'" onclick="window.open(\''. |
| 688 | file_create_url($video->filepath).'\', \'\', \'height='.$height.',width='.$width.'\');">'.$thumb.'</a> '; |
| 689 | } |
| 690 | else { |
| 691 | $output[1] .= $fullsize.' '; |
| 692 | } |
| 693 | if ($count>0 && ++$i >= $count) break; |
| 694 | } |
| 695 | |
| 696 | return $output; |
| 697 | } |
| 698 | |
| 699 | |
| 700 | /************************************************************ |
| 701 | * Gallery functions |
| 702 | ************************************************************/ |
| 703 | |
| 704 | function _node_media_image_gallery($node) { |
| 705 | if (empty($node->node_media[0])) { |
| 706 | drupal_set_message(t('No images uploaded for this content.')); |
| 707 | if (user_access('create node media') && node_access('update', $node)) { |
| 708 | $output = t('Click <a href="!url">here</a> to upload new images.', array('!url' => url('node/'.$node->nid.'/media'))); |
| 709 | } |
| 710 | return '<p>'.$output.'</p>'; |
| 711 | } |
| 712 | |
| 713 | $settings = array('images' => array()); |
| 714 | |
| 715 | $i = 1; |
| 716 | $thumbs = array(); |
| 717 | foreach ($node->node_media[0] as $id=>$image) { |
| 718 | $thumb_path = file_create_url($image->thumbpath); |
| 719 | $thumbs[$i] = array( |
| 720 | 'src' => $thumb_path, |
| 721 | 'title' => $image->description, |
| 722 | ); |
| 723 | |
| 724 | $path = $image->filepath; |
| 725 | $info = image_get_info($path); |
| 726 | $settings['images'][$i++] = array( |
| 727 | 'src' => file_create_url($path), |
| 728 | 'title' => $image->description, |
| 729 | 'description' => $image->description, |
| 730 | 'width' => $info['width'], |
| 731 | 'height' => $info['height'], |
| 732 | 'thumb' => $thumb_path, |
| 733 | ); |
| 734 | } |
| 735 | $total = count($settings['images']); |
| 736 | |
| 737 | if ($total > 0) { |
| 738 | $current = (int)$_GET['page']; |
| 739 | $current = ($current < 1 || $current > $total) ? 1 : $current; |
| 740 | |
| 741 | $settings['current'] = $current; |
| 742 | $settings['total'] = $total; |
| 743 | |
| 744 | $path = drupal_get_path('module', 'node_media'); |
| 745 | drupal_add_js($path.'/node_media.js'); |
| 746 | drupal_add_js(array('slideshow' => array($node->nid => $settings)), 'setting'); |
| 747 | drupal_add_css($path.'/node_media.css'); |
| 748 | |
| 749 | $slideshow = array( |
| 750 | '#type' => 'node_media_gallery', |
| 751 | '#title' => $settings['images'][$current]['title'], |
| 752 | '#attributes' => array('class' => 'mediaslideshow-'. $node->nid), |
| 753 | '#status' => array( |
| 754 | 'current' => $current, |
| 755 | 'total' => $total, |
| 756 | 'previous' => $current < 1 ? $total : $current - 1, |
| 757 | 'next' => $current >= $total ? 1 : $current + 1, |
| 758 | ), |
| 759 | '#media' => $settings['images'][$current], |
| 760 | '#weight' => -5, |
| 761 | ); |
| 762 | } |
| 763 | |
| 764 | list($width, $height) = explode('x', variable_get('upload_max_resolution', 0)); |
| 765 | |
| 766 | $output = ''; |
| 767 | if(variable_get('node_media_gallery_display_node_details_'.$node->type, TRUE)) |
| 768 | $output = node_view($node); |
| 769 | $output .= '<table id="mediaslideshow-table"><tr>'; |
| 770 | $output .= '<td>'.drupal_render($slideshow).'</td>'; |
| 771 | $output .= '<td>'.theme('node_media_gallery_thumbs', $thumbs).'</td>'; |
| 772 | $output .= '</tr></table>'; |
| 773 | //$output .= node_view($node).'<hr />'; |
| 774 | |
| 775 | $title = t('Photo gallery for %title', array('%title' => $node->title)); |
| 776 | drupal_set_title($title); |
| 777 | return $output; |
| 778 | } |
| 779 | |
| 780 | |
| 781 | function _node_media_video_gallery($node) { |
| 782 | if (empty($node->node_media[1])) { |
| 783 | drupal_set_message(t('No images uploaded for this content.')); |
| 784 | if (user_access('create node media') && node_access('update', $node)) { |
| 785 | $output = t('Click <a href="!url">here</a> to upload new videos.', array('!url' => url('node/'.$node->nid.'/media'))); |
| 786 | } |
| 787 | return '<p>'.$output.'</p>'; |
| 788 | } |
| 789 | |
| 790 | $settings = array('videos' => array()); |
| 791 | |
| 792 | $i = 1; |
| 793 | $thumbs = array(); |
| 794 | $iconpath = variable_get('video_media_icon_path','/modules/node_media/img/video_icon.gif'); |
| 795 | foreach ($node->node_media[1] as $id=>$video) { |
| 796 | $thumb_path = file_create_url($video->thumbpath); |
| 797 | $thumbs[$i] = array( |
| 798 | 'src' => $iconpath, |
| 799 | 'title' => $video->description, |
| 800 | ); |
| 801 | |
| 802 | list($width, $height) = explode('x', variable_get('video_media_max_resolution', '400x300')); |
| 803 | |
| 804 | $path = $video->filepath; |
| 805 | $settings['videos'][$i++] = array( |
| 806 | 'src' => file_create_url($path), |
| 807 | 'title' => $video->description, |
| 808 | 'description' => $video->description, |
| 809 | 'width' => $width, |
| 810 | 'height' => $height, |
| 811 | 'thumb' => $iconpath, |
| 812 | 'type' => $video->type |
| 813 | ); |
| 814 | } |
| 815 | $total = count($settings['videos']); |
| 816 | |
| 817 | if ($total > 0) { |
| 818 | $current = (int)$_GET['page']; |
| 819 | $current = ($current < 1 || $current > $total) ? 1 : $current; |
| 820 | |
| 821 | $settings['current'] = $current; |
| 822 | $settings['total'] = $total; |
| 823 | |
| 824 | $path = drupal_get_path('module', 'node_media'); |
| 825 | drupal_add_js($path.'/node_media.js'); |
| 826 | drupal_add_js(array('slideshow' => array($node->nid => $settings)), 'setting'); |
| 827 | drupal_add_css($path.'/node_media.css'); |
| 828 | |
| 829 | $slideshow = array( |
| 830 | '#type' => 'node_media_gallery', |
| 831 | '#title' => $settings['videos'][$current]['title'], |
| 832 | '#attributes' => array('class' => 'mediaslideshow-'. $node->nid), |
| 833 | '#status' => array( |
| 834 | 'current' => $current, |
| 835 | 'total' => $total, |
| 836 | 'previous' => $current < 1 ? $total : $current - 1, |
| 837 | 'next' => $current >= $total ? 1 : $current + 1, |
| 838 | ), |
| 839 | '#media' => $settings['videos'][$current], |
| 840 | '#weight' => -5, |
| 841 | ); |
| 842 | } |
| 843 | |
| 844 | list($width, $height) = explode('x', variable_get('video_media_max_resolution', '400x300')); |
| 845 | |
| 846 | $output = ''; |
| 847 | if(variable_get('node_media_gallery_display_node_details_'.$node->type, TRUE)) |
| 848 | $output = node_view($node).'<hr />'; |
| 849 | $output .= '<table id="mediaslideshow-table"><tr>'; |
| 850 | $output .= '<td>'.drupal_render($slideshow).'</td>'; |
| 851 | $output .= '<td>'.theme('node_media_gallery_thumbs', $thumbs).'</td>'; |
| 852 | $output .= '</tr></table>'; |
| 853 | //$output .= node_view($node).'<hr />'; |
| 854 | |
| 855 | $title = t('Video gallery for %title', array('%title' => $node->title)); |
| 856 | drupal_set_title($title); |
| 857 | return $output; |
| 858 | } |
| 859 | |
| 860 | |
| 861 | function _node_media_sound_gallery($node) { |
| 862 | if (empty($node->node_media[2])) { |
| 863 | drupal_set_message(t('No sounds uploaded for this content.')); |
| 864 | if (user_access('create node media') && node_access('update', $node)) { |
| 865 | $output = t('Click <a href="!url">here</a> to upload new sounds.', array('!url' => url('node/'.$node->nid.'/media'))); |
| 866 | } |
| 867 | return '<p>'.$output.'</p>'; |
| 868 | } |
| 869 | |
| 870 | $settings = array('sounds' => array()); |
| 871 | |
| 872 | $i = 1; |
| 873 | $thumbs = array(); |
| 874 | foreach ($node->node_media[0] as $id=>$image) { |
| 875 | $thumb_path = file_create_url($sound->thumbpath); |
| 876 | $thumbs[$i] = array( |
| 877 | 'src' => $thumb_path, |
| 878 | 'title' => $sound->description, |
| 879 | ); |
| 880 | |
| 881 | $path = $sound->filepath; |
| 882 | $settings['sounds'][$i++] = array( |
| 883 | 'src' => file_create_url($path), |
| 884 | 'title' => $sound->description, |
| 885 | 'description' => $sound->description, |
| 886 | 'width' => variable_get('sound_media_icon_width',32), |
| 887 | 'height' => variable_get('sound_media_icon_height',32), |
| 888 | 'thumb' => $thumb_path, |
| 889 | ); |
| 890 | } |
| 891 | $total = count($settings['sounds']); |
| 892 | |
| 893 | if ($total > 0) { |
| 894 | $current = (int)$_GET['page']; |
| 895 | $current = ($current < 1 || $current > $total) ? 1 : $current; |
| 896 | |
| 897 | $settings['current'] = $current; |
| 898 | $settings['total'] = $total; |
| 899 | |
| 900 | $path = drupal_get_path('module', 'node_media'); |
| 901 | drupal_add_js($path.'/node_media.js'); |
| 902 | drupal_add_js(array('slideshow' => array($node->nid => $settings)), 'setting'); |
| 903 | drupal_add_css($path.'/node_media.css'); |
| 904 | |
| 905 | $slideshow = array( |
| 906 | '#type' => 'node_media_gallery', |
| 907 | '#title' => $settings['sounds'][$current]['title'], |
| 908 | '#attributes' => array('class' => 'mediaslideshow-'. $node->nid), |
| 909 | '#status' => array( |
| 910 | 'current' => $current, |
| 911 | 'total' => $total, |
| 912 | 'previous' => $current < 1 ? $total : $current - 1, |
| 913 | 'next' => $current >= $total ? 1 : $current + 1, |
| 914 | ), |
| 915 | '#media' => $settings['sounds'][$current], |
| 916 | '#weight' => -5, |
| 917 | ); |
| 918 | } |
| 919 | |
| 920 | list($width, $height) = explode('x', variable_get('sound_media_resolution', 0)); |
| 921 | |
| 922 | $output = ''; |
| 923 | if(variable_get('node_media_gallery_display_node_details_'.$node->type, TRUE)) |
| 924 | $output = node_view($node).'<hr />'; |
| 925 | $output .= '<table id="mediaslideshow-table"><tr>'; |
| 926 | $output .= '<td>'.drupal_render($slideshow).'</td>'; |
| 927 | $output .= '<td>'.theme('node_media_gallery_thumbs', $thumbs).'</td>'; |
| 928 | $output .= '</tr></table>'; |
| 929 | //$output .= node_view($node).'<hr />'; |
| 930 | |
| 931 | $title = t('Sound gallery for %title', array('%title' => $node->title)); |
| 932 | drupal_set_title($title); |
| 933 | return $output; |
| 934 | } |
| 935 | |
| 936 | function theme_node_media_gallery($element) { |
| 937 | switch($element['#media']['type']){ |
| 938 | case 0: |
| 939 | $output = '<div'. drupal_attributes($element['#attributes']) .'> |
| 940 | <div class="header"> |
| 941 | '. l(t('Previous'), $_GET['q'], array('class' => 'previous', 'rel' => 'nofollow'), 'page='. $element['#status']['previous']) .' | |
| 942 | '. t('Image !current of !total', array( |
| 943 | '!current' => '<span class="current">'. $element['#status']['current'] .'</span>', |
| 944 | '!total' => '<span class="total">'. $element['#status']['total'] .'</span>')) .' | |
| 945 | '. l(t('Next'), $_GET['q'], array('class' => 'next', 'rel' => 'nofollow'), 'page='. $element['#status']['next']) .' |
| 946 | </div> |
| 947 | <img src="'. url($element['#media']['src']) .'" class="mediapolaroid" /> |
| 948 | <p class="description">'. $element['#title'] .'</p> |
| 949 | </div>'; |
| 950 | break; |
| 951 | case 1: |
| 952 | $output = '<div'. drupal_attributes($element['#attributes']) .'> |
| 953 | <div class="header"> |
| 954 | '. l(t('Previous'), $_GET['q'], array('class' => 'previous', 'rel' => 'nofollow'), 'page='. $element['#status']['previous']) .' | |
| 955 | '. t('Image !current of !total', array( |
| 956 | '!current' => '<span class="current">'. $element['#status']['current'] .'</span>', |
| 957 | '!total' => '<span class="total">'. $element['#status']['total'] .'</span>')) .' | |
| 958 | '. l(t('Next'), $_GET['q'], array('class' => 'next', 'rel' => 'nofollow'), 'page='. $element['#status']['next']) .' |
| 959 | </div> |
| 960 | <embed src="'. url($element['#media']['src']) .'" autostart="0" |
| 961 | showcontrols="1" type="application/x-mplayer2" pluginspage="http://www.microsoft.com/windows/windowsmedia/download/" |
| 962 | height="'. $element['#media']['height'] .'" width="'. $element['#media']['width'] .'"> |
| 963 | <p class="description">'. $element['#title'] .'</p> |
| 964 | </div>'; |
| 965 | break; |
| 966 | case 2; |
| 967 | break; |
| 968 | } |
| 969 | return $output; |
| 970 | } |
| 971 | |
| 972 | function theme_node_media_gallery_thumbs($thumbs, $cols = 2) { |
| 973 | $count = count($thumbs); |
| 974 | $output = '<table id="thumbs-table">'; |
| 975 | |
| 976 | $i = 0; |
| 977 | foreach($thumbs as $id => $thumb) { |
| 978 | if ($i % $cols == 0) $output .= '<tr>'; |
| 979 | $description = '<div class="thumb-description">'.truncate_utf8($thumb['title'], 40, FALSE, TRUE).'</div>'; |
| 980 | $output .= '<td>'.l('<img src="'.$thumb['src'].'" class="slideshow-thumb" id="thumb-'.$id.'" />', $_GET['q'], |
| 981 | array('title' => $thumb['title'], 'rel' => 'nofollow'), 'page='.$id, NULL, FALSE, TRUE).$description.'</td>'; |
| 982 | if ($i % $cols == $cols - 1 || $n >= $count) $output .= "</tr>"; |
| 983 | $i++; |
| 984 | } |
| 985 | |
| 986 | $output .= '</table>'; |
| 987 | return $output; |
| 988 | } |
| 989 | |
| 990 | |
| 991 | /************************************************************ |
| 992 | * Helper functions - admin settings |
| 993 | ************************************************************/ |
| 994 | |
| 995 | /** |
| 996 | * Menu callback; admin settings page. |
| 997 | */ |
| 998 | function node_media_admin_settings() { |
| 999 | _node_media_check_settings(); |
| 1000 | |
| 1001 | $form['node_media_path'] = array( |
| 1002 | '#type' => 'textfield', |
| 1003 | '#title' => t('Node media path'), |
| 1004 | '#default_value' => variable_get('node_media_path', 'node_media'), |
| 1005 | '#maxlength' => 255, |
| 1006 | '#description' => t('Subdirectory in the directory %dir where the node media will be stored. This directory has to exist and be writable by Drupal. You can use the following variables: %uid, %username', array('%dir' => file_directory_path() .'/')), |