[ Index ]

PHP Cross Reference of Drupal 6 (gatewave)

title

Body

[close]

/sites/all/modules/audio/ -> audio.module (source)

   1  <?php
   2  // $Id: audio.module,v 1.159 2009/11/29 19:45:22 drewish Exp $
   3  
   4  /**
   5   * Implementation of hook_help().
   6   */
   7  function audio_help($section, $arg) {
   8    switch ($section) {
   9      case 'audio/by':
  10        return t("You can browse for audio by any of the following fields.");
  11      case 'admin/help#audio':
  12        $help  = '<p>'. t('The audio module allows users to upload and store audio files on a Drupal site. Audio is an important medium for community communication as the recent rise of the <a href="!elink-en-wikipedia-org">podcast phenomenon</a> demonstrates.', array('!elink-en-wikipedia-org' => 'http://en.wikipedia.org/wiki/Podcasting')) .'</p>';
  13        $help .= '<p>'. t('Users create audio nodes by uploading a file from their computer. They are then able to make changes to the metadata, perhaps adding an artist, or removing the track number. Visitors can download the audio file, view the file\'s metadata and encoding information, or browse for audio by metadata (artist, title, year, etc). Visitors can even play MP3s within their browser using the <a href="!elink-musicplayer-sourceforge-net">XSPF flash player</a> that is bundled with the module.', array('!elink-musicplayer-sourceforge-net' => 'http://musicplayer.sourceforge.net/')) .'</p>';
  14        $help .= '<p>'. t('The module uses the <a href="!elink-www-getid3-org">getID3 library</a> to read and write <a href="%elink-en-wikipedia-org">ID3 tag</a> information from the audio file. getID3 can read metadata from a many different audio and video formats giving the audio module a great deal of flexibility.', array('!elink-www-getid3-org' => 'http://www.getid3.org', '!elink-en-wikipedia-org' => 'http://en.wikipedia.org/wiki/Id3')) .'</p>';
  15        $help .= t('<p>You can:</p>
  16                      <ul>
  17                      <li>add an audio file at <a href="!node-add-audio">create content &gt;&gt; audio</a>.</li>
  18                      <li>see your most recent audio files at <a href="!user">user account</a>.</li>
  19                      <li>see all of the most recently added audio files at <a href="!audio">audio</a>.</li>
  20                      <li>browse for audio by its metadata (artist, album, genre, etc) at <a href="!audio-by">audio &gt;&gt; by</a>.</li>
  21                      <li>enable the <em>latest audio</em>, <em>random audio</em>, and <em>browse for audio</em> blocks at <a href="!admin-build-block">administer &gt;&gt; build &gt;&gt; block</a>.</li>
  22                      <li>administer audio module at <a href="!admin-settings-audio">administer &gt;&gt; site configuration &gt;&gt; audio</a>.</li>
  23                      </ul>', array('!audio' => url('audio'), '!audio-by' => url('audio/by'), '!user' => url('user'), '!node-add-audio' => url('node/add/audio'), '!admin-build-block' => url('admin/build/block'), '!admin-settings-audio' => url('admin/settings/audio/audio')));
  24        $help .= '<p>'. t('For more information please read the configuration and customization handbook <a href="!audio">Audio page</a>.', array('!audio' => 'http://www.drupal.org/handbook/modules/audio/')) .'</p>';
  25        return $help;
  26      case 'admin/settings/audio':
  27        $help = '<p><b>'. t('The current PHP configuration limits file uploads to %maxsize.', array('%maxsize' => format_size(file_upload_max_size()))) .'</b><br />';
  28        $help .= '<p>'. t("There are two PHP ini settings, upload_max_filesize and post_max_size, that limit the maximum size of uploads. You can change these settings in the php.ini file or by using a php_value directive in Apache .htaccess file. Consult the PHP documentation for more info.") .'</p>';
  29        return $help;
  30      case 'admin/settings/audio/metadata':
  31        $help = t("These settings let you determine what metadata the audio module tracks. You can add or remove metadata tags and select how they will be used.
  32          <ul>
  33          <li><em>Autocompleted</em> enables javacript autocompletion of the tag based on existing values.</li>
  34          <li><em>Required</em> forces a user to enter a value</li>
  35          <li><em>Hidden</em> prevents the tag from being listed in the node view</li>
  36          <li><em>Browsable</em> allows users to browse for audio using that tag</li>
  37          <li><em>Written to file</em> indicates that the tag should be saved to the file (this requires getid3 support)</li>
  38          <li><em>Weight</em> determines the order of the tags, lower weights are listed first</li>
  39          <li><em>Delete</em> indicates that you would like to remove the tag from the allowed list</li>
  40          </ul>");
  41        $help .= '<p>'. t('<strong>Note:</strong> deleting a tag will not remove it from the database or file until the node is saved again.') .'</p>';
  42        return $help;
  43    }
  44  }
  45  
  46  /**
  47   * Implementation of hook_menu().
  48   */
  49  function audio_menu() {
  50    $items = array();
  51  
  52    $items['admin/settings/audio'] = array(
  53      'title' => 'Audio settings',
  54      'description' => 'Change settings for the audio module.',
  55      'page callback' => 'drupal_get_form',
  56      'page arguments' => array('audio_admin_settings'),
  57      'access arguments' => array('administer site configuration'),
  58      'file' => 'audio.admin.inc',
  59    );
  60    $items['admin/settings/audio/main'] = array(
  61      'title' => 'Audio',
  62      'type' => MENU_DEFAULT_LOCAL_TASK,
  63      'weight' => '-10',
  64    );
  65    $items['admin/settings/audio/metadata'] = array(
  66      'title' => 'Metadata tags',
  67      'page callback' => 'drupal_get_form',
  68      'page arguments' => array('audio_admin_settings_metadata'),
  69      'access arguments' => array('administer audio'),
  70      'file' => 'audio.admin.inc',
  71      'type' => MENU_LOCAL_TASK,
  72    );
  73    $items['admin/settings/audio/players'] = array(
  74      'title' => 'Players',
  75      'page callback' => 'drupal_get_form',
  76      'page arguments' => array('audio_admin_settings_players'),
  77      'access arguments' => array('administer audio'),
  78      'file' => 'audio.admin.inc',
  79      'type' => MENU_LOCAL_TASK,
  80    );
  81  
  82    $items['audio/autocomplete'] = array(
  83      'page callback' => 'audio_autocomplete',
  84      'access arguments' => array('access content'),
  85      'type' => MENU_CALLBACK,
  86    );
  87    $items['audio/by'] = array(
  88      'title' => 'Browse by...',
  89      'page callback' => 'audio_page_browse_by',
  90      'file' => 'audio.pages.inc',
  91      'access arguments' => array('access content'),
  92      'type' => MENU_NORMAL_ITEM,
  93    );
  94    $items['audio/by/%'] = array(
  95      'title' => 'Audio by @tag',
  96      'title arguments' => array('@tag' => 2),
  97      'page callback' => 'audio_page_browse_by',
  98      'page arguments' => array(2),
  99      'file' => 'audio.pages.inc',
 100      'access arguments' => array('access content'),
 101      'type' => MENU_NORMAL_ITEM,
 102    );
 103    $items['audio/by/%/%'] = array(
 104      'title' => 'Audio by @tag @value',
 105      'title arguments' => array('@tag' => 2, '@value' => 3),
 106      'page callback' => 'audio_page_browse_by',
 107      'page arguments' => array(2, 3),
 108      'file' => 'audio.pages.inc',
 109      'access arguments' => array('access content'),
 110      'type' => MENU_NORMAL_ITEM,
 111    );
 112  
 113    $items['audio/download/%node'] = array(
 114      'page callback' => 'audio_download',
 115      'page arguments' => array(2),
 116      'file' => 'audio.pages.inc',
 117      'access callback' => '_audio_allow_download',
 118      'access arguments' => array(2),
 119      'type' => MENU_CALLBACK,
 120    );
 121    $items['audio/play/%node'] = array(
 122      'page callback' => 'audio_play',
 123      'page arguments' => array(2),
 124      'file' => 'audio.pages.inc',
 125      'access callback' => '_audio_allow_play',
 126      'access arguments' => array(2),
 127      'type' => MENU_CALLBACK,
 128    );
 129  
 130    return $items;
 131  }
 132  
 133  /**
 134   * Implementation of hook_theme
 135   */
 136  function audio_theme() {
 137    $theme = array(
 138      'audio_file_form' => array(
 139        'arguments' => array('form'),
 140      ),
 141      'audio_admin_settings_metadata_table' => array(
 142        'arguments' => array('form_element'),
 143        'file' => 'audio.admin.inc',
 144      ),
 145      'audio_admin_settings_players' => array(
 146        'arguments' => array('form_element'),
 147        'file' => 'audio.admin.inc',
 148      ),
 149      'audio_default_node_player' => array(
 150        'arguments' => array('node'),
 151      ),
 152      'audio_teaser' => array(
 153        'arguments' => array('node'),
 154        'file' => 'audio.theme.inc',
 155      ),
 156      'audio_display' => array(
 157        'arguments' => array('node'),
 158        'file' => 'audio.theme.inc',
 159      ),
 160      'audio_format_tag' => array(
 161        'arguments' => array('tag', 'value', 'setting'),
 162        'file' => 'audio.theme.inc',
 163      ),
 164      'audio_format_filelength' => array(
 165        'arguments' => array('fileinfo'),
 166        'file' => 'audio.theme.inc',
 167      ),
 168      'audio_format_fileformat' => array(
 169        'arguments' => array('fileinfo'),
 170        'file' => 'audio.theme.inc',
 171      ),
 172    );
 173  
 174    $player_path = drupal_get_path('module', 'audio') .'/players';
 175    foreach (audio_get_players('names') as $name => $player) {
 176      if ($player['module'] == 'audio') {
 177        $theme[$player['theme_node']] = array(
 178          'arguments' => array('node', 'options'),
 179          'file' => $player['file'],
 180          'path' => $player_path,
 181        );
 182        if (!empty($player['theme_xspf'])) {
 183          $theme[$player['theme_xspf']] = array(
 184            'arguments' => array('path', 'options'),
 185            'file' => $player['file'],
 186            'path' => $player_path,
 187          );
 188        }
 189      }
 190    }
 191    return $theme;
 192  }
 193  
 194  /**
 195   * Implementation of hook_node_info().
 196   */
 197  function audio_node_info() {
 198    return array(
 199      'audio' => array(
 200        'name' => t('Audio'),
 201        'module' => 'audio',
 202        'description' => t('An audio file. The audio file could be used for adding music, podcasts, or audio clips to your site.'),
 203      )
 204    );
 205  }
 206  
 207  /**
 208   * Invoke hook_audio api methods.
 209   *
 210   * The audio module provide a hook for contributed modules. Use this rather than
 211   * the nodeapi so you don't have to worry about the module invocation order.
 212   *
 213   *   function hook_audio($op, $node, $arg1)
 214   *
 215   * @param $op
 216   *   The $op value will be one of the following:
 217   *   'upload'
 218   *       A new audio file has been uploaded. Contrib modules can read data from
 219   *       the file and append it to the audio node at this point.
 220   *   'access'
 221   *       An operation is being performed on the node and other modules can
 222   *       determine if this will be allowed. $arg1 will describe the operation
 223   *       (i.e. 'play' or 'download'). Return a boolean if you want to allow or
 224   *       deny this, or NULL if you don't.
 225   *   'download'
 226   *       A user is playing the audio node. This can be handy if you're recording
 227   *       user statistics.
 228   *   'play'
 229   *       A user is downloading an audio node. This can be handy if you're
 230   *       recording user statistics.
 231   * @param $node
 232   *   An audio node object.
 233   */
 234  function audio_invoke_audioapi($op, &$node, $a3 = NULL, $a4 = NULL) {
 235    $return = array();
 236    foreach (module_implements('audio') as $name) {
 237      $function = $name .'_audio';
 238      $result = $function($op, $node, $a3, $a4);
 239      if (isset($result) && is_array($result)) {
 240        $return = array_merge($return, $result);
 241      }
 242      else if (isset($result)) {
 243        $return[] = $result;
 244      }
 245    }
 246    return $return;
 247  }
 248  
 249  /**
 250   * Implementation of hook_perm().
 251   */
 252  function audio_perm() {
 253    return array('administer audio', 'create audio', 'edit own audio',
 254      'play audio', 'download audio', 'view download stats');
 255  }
 256  
 257  /**
 258   * Implementation of hook_access().
 259   */
 260  function audio_access($op, $node = NULL) {
 261    global $user;
 262  
 263    if (user_access('administer audio')) {
 264      return TRUE;
 265    }
 266  
 267    if ($op == 'update' || $op == 'delete') {
 268      if (($user->uid == $node->uid) && user_access('edit own audio')) {
 269        return TRUE;
 270      }
 271    }
 272    if ($op == 'create') {
 273      if (user_access('create audio')) {
 274        return TRUE;
 275      }
 276    }
 277  }
 278  
 279  /**
 280   * Is the current user allowed to download an audio node?
 281   *
 282   * @return
 283   *   boolean indicating if it's allowed.
 284   */
 285  function _audio_allow_download($node) {
 286    if ($node->type == 'audio' && isset($node->url_download) && $node->audio['downloadable']) {
 287      $result = audio_invoke_audioapi('access', $node, 'download');
 288  
 289      if (in_array(TRUE, $result)) {
 290        return TRUE;
 291      }
 292      if (in_array(FALSE, $result)) {
 293        return FALSE;
 294      }
 295      return user_access('download audio');
 296    }
 297    return FALSE;
 298  }
 299  
 300  /**
 301   * Is the current user allowed to play an audio node?
 302   *
 303   * @return
 304   *   boolean indicating if it's allowed.
 305   */
 306  function _audio_allow_play($node) {
 307    if ($node->type == 'audio' && isset($node->url_play)) {
 308      $result = audio_invoke_audioapi('access', $node, 'play');
 309  
 310      if (in_array(TRUE, $result)) {
 311        return TRUE;
 312      }
 313      if (in_array(FALSE, $result)) {
 314        return FALSE;
 315      }
 316      return user_access('play audio');
 317    }
 318    return FALSE;
 319  }
 320  
 321  /**
 322   * Implements hook_cron().
 323   *
 324   * This deletes old temp files.
 325   */
 326  function audio_cron() {
 327    $path = audio_get_directory() .'/temp';
 328    $files = file_scan_directory(file_create_path($path), '.*');
 329    foreach ($files as $file => $info) {
 330      if (time() - filemtime($file) > 60*60*6) {
 331        file_delete($file);
 332      }
 333    }
 334  }
 335  
 336  /**
 337   * Implementation of hook_link().
 338   */
 339  function audio_link($type, $node, $main = 0) {
 340    $links = array();
 341    $link_access = user_access('view download stats');
 342  
 343    if ($type == 'node' && $node->type == 'audio') {
 344      if (_audio_allow_download($node)) {
 345        $links['audio_download_link'] = array(
 346          'title' => t('Download audio file'),
 347          'href' => $node->url_download,
 348        );
 349        if ($link_access) {
 350          $links['audio_download_count'] = array(
 351            'title' => t('@download_count downloads', array('@download_count' => $node->audio['download_count'])),
 352          );
 353        }
 354      }
 355      if (_audio_allow_play($node) && $link_access) {
 356        $links['audio_play_count'] = array(
 357          'title' => t('@play_count plays', array('@play_count' => $node->audio['play_count'])),
 358        );
 359      }
 360    }
 361  
 362    return $links;
 363  }
 364  
 365  /**
 366   * Implementation of hook_nodeapi().
 367   */
 368  function audio_nodeapi(&$node, $op, $arg) {
 369    if ($node->type == 'audio') {
 370      switch ($op) {
 371        case 'delete revision':
 372          audio_delete_revision($node);
 373          break;
 374  
 375        case 'rss item':
 376          $ret = array();
 377          if (_audio_allow_download($node)) {
 378            // Reset the node's body to remove theming.
 379            $body = db_result(db_query("SELECT r.body FROM {node} n INNER JOIN {node_revisions} r ON n.nid = r.nid WHERE n.vid=%d", $node->vid));
 380            $node->body = $body;
 381            $node = node_prepare($node, FALSE);
 382  
 383            $ret[] = array(
 384              'key' => 'enclosure',
 385              'value' => '',
 386              'attributes' => array(
 387                'url' => $node->url_download,
 388                'length' => $node->audio['file']->filesize,
 389                'type' => $node->audio['file']->filemime
 390              ));
 391            // Provide very basic iTunes support.
 392            $ret[] = array(
 393              'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
 394              'key' => 'itunes:duration',
 395              'value' => $node->audio['playtime'],
 396            );
 397            $ret[] = array(
 398              'namespace' => array('xmlns:itunes' => 'http://www.itunes.com/dtds/podcast-1.0.dtd'),
 399              'key' => 'itunes:author',
 400              'value' => $node->audio_tags['artist'],
 401            );
 402          }
 403          return $ret;
 404  
 405        case 'update index':
 406          // Since the theme might hide the tag values we'll manually add them to
 407          // the search index.
 408          $cleantags = array();
 409          foreach ($node->audio_tags as $value) {
 410            $cleantags[] = check_plain($value);
 411          }
 412          return $cleantags;
 413      }
 414    }
 415  }
 416  
 417  /**
 418   * Implementation of hook_view().
 419   */
 420  function audio_view(&$node, $teaser = FALSE, $page = FALSE) {
 421    drupal_add_css(drupal_get_path('module', 'audio') .'/audio.css');
 422  
 423    $node = node_prepare($node, $teaser);
 424    if ($teaser) {
 425      $node->content['audio'] = array(
 426        '#value' => theme('audio_teaser', $node),
 427        '#weight' => 0,
 428      );
 429    }
 430    else {
 431      $node->content['audio'] = array(
 432        '#value' => theme('audio_display', $node),
 433        '#weight' => -1,
 434      );
 435    }
 436    return $node;
 437  }
 438  
 439  /**
 440   * Implementation of hook_validate().
 441   */
 442  function audio_validate(&$node, &$form) {
 443    if (!isset($node->audio['file']->filepath)) {
 444      form_set_error('audio_upload', t("A file must be provided. If you tried uploading a file, make sure it's less than the upload size limit."));
 445    }
 446  
 447    if (empty($form_state['values']['title_format'])) {
 448      $form_state['values']['title_format'] =  variable_get('audio_default_title_format', '[audio-tag-title-raw] by [audio-tag-artist-raw]');
 449    }
 450  }
 451  
 452  /**
 453   * Implementation of hook_load().
 454   */
 455  function audio_load($node) {
 456    if ($node->vid) {
 457      // This is a wonky way to load the fields but its handy right now while
 458      // I'm renaming fields in the databases.
 459      $fields = db_fetch_array(db_query("SELECT a.* FROM {audio} a WHERE vid=%d", $node->vid));
 460      $file = db_fetch_object(db_query("SELECT * FROM {files} WHERE fid=%d", $fields['fid']));
 461  
 462      $ret = array(
 463        'title_format' => $fields['title_format'],
 464        'audio' => array(
 465          'play_count'     => $fields['play_count'],
 466          'download_count' => $fields['download_count'],
 467          'downloadable'   => $fields['downloadable'],
 468          'format'         => $fields['format'],
 469          'sample_rate'    => $fields['sample_rate'],
 470          'channel_mode'   => $fields['channel_mode'],
 471          'bitrate'        => $fields['bitrate'],
 472          'bitrate_mode'   => $fields['bitrate_mode'],
 473          'playtime'       => $fields['playtime'],
 474          'bitrate'        => $fields['bitrate'],
 475          'file'           => $file,
 476        ),
 477      );
 478  
 479      if (isset($file->filepath) && file_exists($file->filepath)) {
 480        // TODO: should these links be by vid?
 481        $ret['url_play'] = url('audio/play/'. $node->nid, array('absolute' => TRUE));
 482        if ($ret['audio']['downloadable']) {
 483          // iTunes and other podcasting programs check the url to determine the
 484          // file type. we'll add the original file name on to the end. see issues
 485          // #35398 and #68716 for more info.
 486          $url = 'audio/download/'. $node->nid .'/'. $file->filename;
 487          $ret['url_download'] = url($url, array('absolute' => TRUE));
 488        }
 489      }
 490  
 491      // Load the audio tags.
 492      $result = db_query("SELECT tag, value FROM {audio_metadata} WHERE vid=%d", $node->vid);
 493      $ret['audio_tags'] = array();
 494      while ($obj = db_fetch_object($result)) {
 495        $ret['audio_tags'][$obj->tag] = $obj->value;
 496      }
 497  
 498      return $ret;
 499    }
 500    return array();
 501  }
 502  
 503  /**
 504   * Implementation of hook_insert().
 505   */
 506  function audio_insert(&$node) {
 507    file_move($node->audio['file']->filepath, audio_get_directory(), FILE_EXISTS_RENAME);
 508    _audio_save($node);
 509  }
 510  
 511  /**
 512   * Implementation of hook_update().
 513   */
 514  function audio_update(&$node) {
 515    // If there's a new file we need to move it to the correct location.
 516    if (($node->audio['file']->status & FILE_STATUS_PERMANENT) != FILE_STATUS_PERMANENT) {
 517      if (!$node->revision) {
 518        // If it's not a revision the existing file needs to be removed.
 519        $oldfile = db_fetch_object(db_query('SELECT f.* FROM {files} f INNER JOIN {audio} a ON f.fid = a.fid WHERE a.vid = %d', $node->vid));
 520        _audio_file_delete($oldfile);
 521      }
 522      file_move($node->audio['file']->filepath, audio_get_directory(), FILE_EXISTS_RENAME);
 523    }
 524    else if ($node->revision) {
 525      // New revision using existing file so we need to copy it.
 526      file_copy($node->audio['file']->filepath, file_create_filename($node->audio['file']->filename, audio_get_directory()));
 527      // Unset the fid so a new record is created.
 528      $node->audio['file']->fid = NULL;
 529    }
 530  
 531    _audio_save($node);
 532  }
 533  
 534  /**
 535   * Handle the busy work of saving the node's audio and file records.
 536   *
 537   * @param $node Node opject.
 538   */
 539  function _audio_save(&$node) {
 540    // Save the file
 541    $node->audio['file']->timestamp = time();
 542    $node->audio['file']->filesize = filesize($node->audio['file']->filepath);
 543    $node->audio['file']->status |= FILE_STATUS_PERMANENT;
 544    // If there's an fid update, otherwise insert.
 545    $file_update = empty($node->audio['file']->fid) ? array() : array('fid');
 546    drupal_write_record('files', $node->audio['file'], empty($node->audio['file']->fid) ? array() : array('fid'));
 547  
 548    // Save the audio row.
 549    $audio = array_merge($node->audio, array('nid' => $node->nid, 'vid' => $node->vid, 'title_format' => $node->title_format, 'fid' => $node->audio['file']->fid));
 550    $audio_update = ($node->is_new || $node->revision) ? array() : array('vid');
 551    drupal_write_record('audio', $audio, $audio_update);
 552  
 553    // Remove any existing metadata.
 554    db_query("DELETE FROM {audio_metadata} WHERE vid=%d", $node->vid);
 555  
 556    // Save the new tags.
 557    $allowed_tags = audio_get_tags_allowed();
 558    foreach ($node->audio_tags as $tag => $value) {
 559      if (in_array($tag, $allowed_tags) && $value) {
 560        $metadata = array('vid' => $node->vid, 'tag' => $tag, 'value' => $value, 'clean' => audio_clean_tag($value));
 561        drupal_write_record('audio_metadata', $metadata);
 562      }
 563    }
 564  }
 565  
 566  /**
 567   * Implementation of hook_delete().
 568   */
 569  function audio_delete($node) {
 570    $result = db_query('SELECT a.vid, f.* FROM {audio} a LEFT JOIN {files} f ON a.fid = f.fid WHERE nid = %d', $node->nid);
 571    while ($o = db_fetch_object($result)) {
 572      _audio_file_delete($o);
 573      db_query('DELETE FROM {audio_metadata} WHERE vid = %d', $o->vid);
 574    }
 575    db_query('DELETE FROM {audio} WHERE nid = %d', $node->nid);
 576  }
 577  
 578  /**
 579   * Delete a single revision.
 580   */
 581  function audio_delete_revision($node) {
 582    $file = db_result(db_query('SELECT a.vid, f.* FROM {audio} a LEFT JOIN {files} f ON a.fid = f.fid WHERE vid = %d', $node->vid));
 583    _audio_file_delete($file);
 584  
 585    db_query('DELETE FROM {audio_metadata} WHERE vid = %d', $node->vid);
 586    db_query('DELETE FROM {audio} WHERE vid = %d', $node->vid);
 587  }
 588  
 589  /**
 590   * Handle the busy work of deleting the file record and the file.
 591   *
 592   * @param unknown_type $file
 593   */
 594  function _audio_file_delete($file) {
 595    if (!empty($file->filepath)) {
 596      file_delete($file->filepath);
 597    }
 598    if (!empty($file->fid)) {
 599      db_query('DELETE FROM {files} WHERE fid = %d', array($file->fid));
 600    }
 601  }
 602  
 603  /**
 604   * Implementation of hook_form().
 605   */
 606  function audio_form(&$node, &$form_state) {
 607    drupal_add_js(drupal_get_path('module', 'audio') .'/audio.js');
 608  
 609    $type = node_get_types('type', $node);
 610    if ($type->has_title) {
 611      $form['title']['#weight'] = -5;
 612      $form['title']['title_format'] = array(
 613        '#type' => 'textfield',
 614        '#title' => check_plain($type->title_label),
 615        '#default_value' => !empty($node->title_format) ? $node->title_format : variable_get('audio_default_title_format', '[audio-tag-title-raw] by [audio-tag-artist-raw]'),
 616        '#description' => t("The title can use the file's metadata. You can use the tokens listed below to insert information into the title. <strong>Note:</strong> the node title is escaped so it is safe to use the -raw tokens."),
 617        '#required' => TRUE,
 618      );
 619      $form['title']['token_help'] = array(
 620        '#title' => t('Token list'),
 621        '#type' => 'fieldset',
 622        '#collapsible' => TRUE,
 623        '#collapsed' => TRUE,
 624        '#description' => t('This is a list of the tokens that can be used in the title of audio nodes.'),
 625        'help' => array('#value' => theme('token_help', 'node')),
 626      );
 627    }
 628  
 629    if ($type->has_body) {
 630      $form['body_filter']['#weight'] = -4;
 631      $form['body_filter']['body'] = array(
 632        '#type' => 'textarea',
 633        '#title' => check_plain($type->body_label),
 634        '#default_value' => $node->body,
 635        '#rows' => 5,
 636        '#required' => ($type->min_word_count > 0),
 637      );
 638      $form['body_filter']['format'] = filter_form($node->format);
 639    }
 640  
 641  
 642    $form['audio'] = array(
 643      '#type' => 'fieldset',
 644      '#title' => t('Audio File Info'),
 645      '#collapsible' => TRUE,
 646      '#weight' => -1,
 647      '#tree' => TRUE,
 648    );
 649  
 650    // Place a visible copy of the file path on the form (after removing the
 651    // directory info from non-admins).
 652    $form['audio']['display_filepath'] = array(
 653      '#type' => 'item',
 654      '#title' => t('Current File'),
 655      '#value' => empty($node->audio['file']->filepath) ? t('No file is attached.') : (user_access('administer audio') ? $node->audio['file']->filepath : basename($node->audio['file']->filepath)),
 656    );
 657  
 658    // If we've got a file, add the file information fields.
 659    if (!empty($node->audio['file']->filename)) {
 660      $form['audio']['#theme'] = 'audio_file_form';
 661  
 662      // Store the non-user editable file information as values.
 663      $form['audio']['file'] = array(
 664        '#type' => 'value',
 665        '#value' => $node->audio['file'],
 666      );
 667  
 668      $form['audio']['format'] = array(
 669        '#type' => 'select',
 670        '#title' => t('Format'),
 671        '#default_value' => $node->audio['format'],
 672        '#options' => drupal_map_assoc(array('', 'aac', 'ac3', 'au', 'avr', 'flac', 'midi', 'mod', 'mp3', 'mpc', 'ogg', 'voc'), 'drupal_strtoupper'),
 673      );
 674      $form['audio']['file_size'] = array(
 675        '#type' => 'textfield',
 676        '#title' => t('File Size'),
 677        '#default_value' => $node->audio['file']->filesize,
 678      );
 679      $form['audio']['playtime'] = array(
 680        '#type' => 'textfield',
 681        '#title' => t('Length'),
 682        '#default_value' => $node->audio['playtime'],
 683        '#description' => t('The format is hours:minutes:seconds.'),
 684      );
 685      $form['audio']['sample_rate'] = array(
 686        '#type' => 'select',
 687        '#title' => t('Sample rate'),
 688        '#default_value' => $node->audio['sample_rate'],
 689        '#options' => array('' => '', '48000' => '48,000 Hz', '44100' => '44,100 Hz', '32000' => '32,000 Hz', '22050' => '22,050 Hz', '11025' => '11,025 Hz', '8000' => '8,000 Hz'),
 690      );
 691      $form['audio']['channel_mode'] = array(
 692        '#type' => 'select',
 693        '#title' => t('Channel mode'),
 694        '#default_value' => $node->audio['channel_mode'],
 695        '#options' => array('stereo' => t('Stereo'), 'mono' => t('Mono')),
 696      );
 697      $form['audio']['bitrate'] = array(
 698        '#type' => 'textfield',
 699        '#title' => t('Bitrate'),
 700        '#default_value' => $node->audio['bitrate'],
 701      );
 702      $form['audio']['bitrate_mode'] = array(
 703        '#type' => 'select',
 704        '#title' => t('Bitrate mode'),
 705        '#default_value' => $node->audio['bitrate_mode'],
 706        '#options' => array('' => '', 'cbr' => t('Constant'), 'vbr' => t('Variable')),
 707      );
 708  
 709      // Users shouldn't be able to change the play and download counts so we'll
 710      // put these for viewing...
 711      $form['audio']['display_play_count'] = array(
 712        '#type' => 'item',
 713        '#title' => t('Play count'),
 714        '#value' => $node->audio['play_count'],
 715      );
 716      $form['audio']['display_download_count'] = array(
 717        '#type' => 'item',
 718        '#title' => t('Download count'),
 719        '#value' => $node->audio['download_count'],
 720      );
 721      // ...and these are what we'll save back to the node.
 722      $form['audio']['play_count'] = array(
 723        '#type' => 'value',
 724        '#value' => $node->audio['play_count'],
 725      );
 726      $form['audio']['download_count'] = array(
 727        '#type' => 'value',
 728        '#value' => $node->audio['download_count'],
 729      );
 730    }
 731  
 732    $extensions = implode(', ', array_filter(explode(' ', variable_get('audio_allowed_extensions', 'mp3 wav ogg'))));
 733    $form['audio']['audio_upload'] = array(
 734      '#tree' => FALSE,
 735      '#type' => 'file',
 736      '#title' => empty($node->audio['file']->filename) ? t('Add a new audio file') : t('Replace this with a new file'),
 737      '#attributes' => array('audio_accept' => $extensions),
 738      '#description' => t('Click "Browse..." to select an audio file to upload. Only files with the following extensions are allowed: %allowed-extensions.', array('%allowed-extensions' => $extensions)) .'<br />'
 739        . t('<strong>NOTE:</strong> the current PHP configuration limits uploads to %maxsize.', array('%maxsize' => format_size(file_upload_max_size()))),
 740    );
 741    $form['audio']['downloadable'] = array(
 742      '#type' => 'checkbox',
 743      '#title' => t('Allow file downloads.'),
 744      '#default_value' => isset($node->audio['downloadable']) ? $node->audio['downloadable'] : variable_get('audio_default_downloadable', 1),
 745      '#description' => t('If checked, a link will be displayed allowing visitors to download this audio file on to their own computer.') .'<br />'
 746        . t('<strong>WARNING:</strong> even if you leave this unchecked, clever users will be able to find a way to download the file. This just makes them work a little harder to find the link.'),
 747    );
 748  
 749  
 750    // If we've got a file, add the fields for editing meta data.
 751    if (!empty($node->audio['file']->filepath)) {
 752      $form['audio_tags'] = array(
 753        '#type' => 'fieldset',
 754        '#title' => t('Audio Metadata'),
 755        '#collapsible' => TRUE,
 756        '#tree' => TRUE,
 757        '#weight' => -3,
 758      );
 759      // Delegate out the dirty work of building form elements.
 760      foreach (audio_get_tag_settings() as $tag => $tag_settings) {
 761        $form['audio_tags'][$tag] = _audio_build_tag_form($tag, $tag_settings, empty($node->audio_tags[$tag]) ? '' : $node->audio_tags[$tag]);
 762      }
 763    }
 764  
 765    $form['#validate'][] = 'audio_node_form_validate';
 766    $form['#submit'][] = 'audio_node_form_submit';
 767    $form['#attributes'] = array('enctype' => 'multipart/form-data');
 768  
 769    return $form;
 770  }
 771  
 772  /**
 773   * Theme function to put the form elements into a table. Steal some CSS from
 774   * the watchdog module.
 775   */
 776  function theme_audio_file_form($form) {
 777    // List of elements to leave out of the table.
 778    $skip_list = array('audio_upload' => 1, 'downloadable' => 1);
 779  
 780    $rows = array();
 781    foreach (element_children($form) as $key) {
 782      if (!isset($skip_list[$key])) {
 783        if ($form[$key]['#type'] != 'hidden' && $form[$key]['#type'] != 'value') {
 784          $title = $form[$key]['#title'];
 785          unset($form[$key]['#title']);
 786          $rows[] = array(
 787            array('data' => $title, 'header' => TRUE),
 788            drupal_render($form[$key]),
 789          );
 790        }
 791      }
 792    }
 793  
 794    $output = theme('table', array(), $rows, array('class' => 'watchdog-event'));
 795    return $output . drupal_render($form);
 796  }
 797  
 798  /**
 799   * Node submit handler
 800   */
 801  function audio_node_form_validate($form, &$form_state) {
 802    // Check for an upload.
 803    $validators = array(
 804      'file_validate_extensions' => array(variable_get('audio_allowed_extensions', 'mp3 wav ogg')),
 805    );
 806    if ($file = file_save_upload('audio_upload', $validators)) {
 807      $node = (object) $form_state['values'];
 808      $node->audio = array(
 809        'play_count' => 0,
 810        'download_count' => 0,
 811        'downloadable' => (bool) $form_state['values']['audio']['downloadable'],
 812        'file' => $file,
 813      );
 814  
 815      // Allow other modules to modify the node.
 816      audio_invoke_audioapi('upload', $node);
 817  
 818      // ...save info so that it shows up in both the preview and
 819      // form. Note that we do this after calling we called our api hook with
 820      // the upload operation, it gives the audio_id3 module a chance to read
 821      // the tags.
 822      $form_state['values']['audio'] = $node->audio;
 823      $form_state['values']['audio_tags'] = $node->audio_tags;
 824      $form_state['values']['audio_images'] = $node->audio_images;
 825    }
 826  }
 827  
 828  /**
 829   * Node submit handler
 830   */
 831  function audio_node_form_submit($form, &$form_state) {
 832    $node = (object) $form_state['values'];
 833  
 834    // Use the token module to build the title. Clear the existing title to
 835    // prevent the [title] tag from being expanded.
 836    $node->title = '';
 837    token_get_values('node', $node, TRUE);
 838    $form_state['values']['title'] = token_replace($form_state['values']['title_format'], 'node', $node);
 839  }
 840  
 841  /**
 842   * Construct a form element to represent a tag.
 843   *
 844   * @param $tag
 845   *   Name of the tag.
 846   * @param $tag_settings
 847   *   Array of settings that determine the way the tag is handled.
 848   * @param $value
 849   *   Value of the tag.
 850   * @return
 851   *  Array with a form element.
 852   */
 853  function _audio_build_tag_form($tag, $tag_settings, $value) {
 854    $form_element = array(
 855      '#type' => 'textfield',
 856      '#title' => t(ucwords(str_replace('_', ' ', $tag))),
 857      '#default_value' => $value,
 858      '#maxlength' => 255,
 859      '#required' => (boolean) $tag_settings['required'],
 860    );
 861  
 862    if ($tag_settings['autocomplete']) {
 863      $form_element['#autocomplete_path'] = 'audio/autocomplete/'. $tag;
 864    }
 865  
 866    switch ($tag) {
 867      case 'track':
 868        $form_element['#title'] = t('Track Number');
 869        $form_element['#maxlength'] = 10;
 870        $form_element['#description'] = t("Enter either a single number or fraction here. '1' means that this is the first track on the album, and '1/8' .");
 871        break;
 872  
 873      case 'genre':
 874        $form_element['#maxlength'] = 40;
 875        break;
 876    }
 877  
 878    return $form_element;
 879  }
 880  
 881  
 882  /**
 883   * Implementation of hook_user().
 884   */
 885  function audio_user($type, &$edit, &$user) {
 886    if ($type == 'view' && db_result(db_query(db_rewrite_sql("SELECT COUNT(*) FROM {node} n WHERE n.type = 'audio' AND n.uid = %d AND n.status = 1"), $user->uid))) {
 887      $user->content['summary']['audio'] =  array(
 888        '#type' => 'user_profile_item',
 889        '#title' => t('Audio'),
 890        '#value' => l(t("Listen to @username's recent audio files", array('@username' => $user->name)), "audio/user/$user->uid"),
 891        '#attributes' => array('class' => 'audio'),
 892      );
 893    }
 894  }
 895  
 896  /**
 897   * Implementation of hook_block().
 898   *
 899   * Provides a block for browsing by metadata.
 900   */
 901  function audio_block($op = 'list', $delta = 0, $edit = array()) {
 902    switch ($op) {
 903      case 'list':
 904        $blocks[2]['info'] = t('Audio: Browse by');
 905        return $blocks;
 906  
 907      case 'view':
 908        if (user_access('access content')) {
 909          switch ($delta) {
 910            case 2:
 911              $items = array();
 912              $settings = audio_get_tag_settings();
 913              $result = db_query(db_rewrite_sql('SELECT DISTINCT a.tag FROM {node} n INNER JOIN {audio_metadata} a ON n.vid = a.vid WHERE n.status = 1 ORDER BY a.tag ASC'));
 914              while ($obj = db_fetch_object($result)) {
 915                if ($settings[$obj->tag]['browsable']) {
 916                  $items[] = l($obj->tag, 'audio/by/'. $obj->tag);
 917                }
 918              }
 919              $block['subject'] = t('Browse for audio by');
 920              $block['content'] = theme('item_list', $items);
 921              break;
 922          }
 923          return $block;
 924        }
 925    }
 926  }
 927  
 928  /**
 929   * Implementation of hook_file_download().
 930   */
 931  function audio_file_download($filename) {
 932    $filepath = file_create_path($filename);
 933  
 934    // Check if it's one of our files.
 935    if ($file = db_fetch_object(db_query("SELECT a.nid, f.* FROM {audio} a INNER JOIN {files} f ON a.fid = f.fid WHERE f.filepath = '%s'", $filepath))) {
 936      if (isset($file->nid) && $node = node_load($file->nid)) {
 937        // Make sure they're allowed to view the node.
 938        if (node_access('view', $node)) {
 939          return array(
 940            'Content-Type: '. mime_header_encode($file->filemime),
 941            'Content-Length: '. (int) $file->filesize,
 942          );
 943        }
 944  
 945        // Access denied.
 946        return -1;
 947      }
 948    }
 949  }
 950  /**
 951   * Get an array of the allowed tags.
 952   *
 953   * @return
 954   *   Array of allowed tags.
 955   */
 956  function audio_get_tags_allowed() {
 957    return array_keys(audio_get_tag_settings());
 958  }
 959  
 960  /**
 961   * Get an array of the tags and their settings.
 962   *
 963   * @return
 964   *   Array of allowed tags.
 965   */
 966  function audio_get_tag_settings() {
 967    $defaults = array(
 968      'artist' => array('autocomplete' => 1, 'required' => 1, 'hidden' => 0,
 969        'writetofile' => 1, 'browsable' => 1, 'weight' => -2),
 970      'title'  => array('autocomplete' => 0, 'required' => 1, 'hidden' => 0,
 971         'writetofile' => 1, 'browsable' => 1, 'weight' => -2),
 972      'album'  => array('autocomplete' => 1, 'required' => 0, 'hidden' => 0,
 973         'writetofile' => 1, 'browsable' => 1, 'weight' => -1),
 974      'track'  => array('autocomplete' => 0, 'required' => 0, 'hidden' => 0,
 975         'writetofile' => 1, 'browsable' => 0, 'weight' => -1),
 976      'genre'  => array('autocomplete' => 1, 'required' => 0, 'hidden' => 0,
 977         'writetofile' => 1, 'browsable' => 1, 'weight' => 0),
 978      'year'   => array('autocomplete' => 0, 'required' => 0, 'hidden' => 0,
 979         'writetofile' => 1, 'browsable' => 1, 'weight' => 1),
 980    );
 981    return variable_get('audio_tag_settings', $defaults);
 982  }
 983  
 984  /**
 985   * Retrieve autocomplete suggestions for existing audio metadata tags.
 986   *
 987   * @param $tag
 988   *   tag listed in audio_get_tag_settings() where autocomplete is true.
 989   * @param $value
 990   *   partial tag value to try to find in the database.
 991   */
 992  function audio_autocomplete($tag = '', $value = '') {
 993    $matches = array();
 994    $tags = audio_get_tag_settings();
 995    if (isset($tags[$tag]) && $tags[$tag]['autocomplete']) {
 996      $result = db_query_range("SELECT DISTINCT a.value FROM {audio_metadata} a WHERE a.tag = '%s' AND LOWER(a.value) LIKE LOWER('%s%%') ORDER BY a.value", $tag, $value, 0, 10);
 997      while ($tag = db_fetch_object($result)) {
 998        $matches[$tag->value] = check_plain($tag->value);
 999      }
1000    }
1001    print drupal_to_js($matches);
1002    exit();
1003  }
1004  
1005  /**
1006   * Get the audio files directory.
1007   *
1008   * @return
1009   *   string path to the audio directory
1010   */
1011  function audio_get_directory() {
1012    // We have to make sure the files directory exists first...
1013    $dir = file_directory_path();
1014    file_check_directory($dir, FILE_CREATE_DIRECTORY, 'made up element name');
1015    // ...then that our audio sub-directory exists.
1016    $dir .= '/audio';
1017    file_check_directory($dir, FILE_CREATE_DIRECTORY, 'made up element name');
1018  
1019    return $dir;
1020  }
1021  
1022  
1023  /**
1024   * Loads the player plugs-ins and returns their details.
1025   *
1026   * @return
1027   *   Array with two sub arrays. The first keyed by player name, the second
1028   *   keyed by format and then player name. The same player may be listed for
1029   *   mulaudio_page_browse_bytiple formats.
1030   */
1031  function _audio_player_build_list() {
1032    // Invoke any modules that implement the hook.
1033    $players_name = module_invoke_all('audio_player');
1034  
1035    // Load all our module's player plugins.
1036    $path = drupal_get_path('module', 'audio') .'/players';
1037    $files = drupal_system_listing('.inc$', $path, 'name', 0);
1038    foreach ($files as $file) {
1039      require_once('./'. $file->filename);
1040      $function = 'audio_'. $file->name .'_audio_player';
1041      if (function_exists($function)) {
1042        $result = $function();
1043        if (isset($result) && is_array($result)) {
1044          foreach (array_keys($result) as $player) {
1045            // Keep track of the file where this player was found.
1046            $result[$player]['file'] = $file->basename;
1047          }
1048          $players_name = array_merge($players_name, $result);
1049        }
1050      }
1051    }
1052  
1053    // Group players by format.
1054    $players_format = array();
1055    foreach ($players_name as $name => $player) {
1056      foreach ($player['formats'] as $format) {
1057        $players_format[$format][$name] = $player;
1058      }
1059    }
1060  
1061    return array($players_name, $players_format);
1062  }
1063  
1064  /**
1065   * Return information on the installed player plugins.
1066   *
1067   * @param $op
1068   *   String specifying the operation. Possible values are:
1069   *     'formats'
1070   *     'format'
1071   *     'names'
1072   *     'name'
1073   * @param $name
1074   *   Name of a specific player, used with $op = 'format' or 'name'.
1075   */
1076  function audio_get_players($op = 'names', $name = '') {
1077    static $_player_name, $_player_format;
1078  
1079    if (!isset($_player_format)) {
1080      list($_player_name, $_player_format) = _audio_player_build_list();
1081    }
1082  
1083    switch ($op) {
1084      case 'formats':
1085        return $_player_format;
1086      case 'format':
1087        if (isset($_player_format[$name])) {
1088          return $_player_format[$name];
1089        }
1090        return FALSE;
1091      case 'names':
1092        return $_player_name;
1093      case 'name':
1094        if (isset($_player_name[$name])) {
1095          return $_player_name[$name];
1096        }
1097        return FALSE;
1098      default:
1099        return FALSE;
1100    }
1101  }
1102  
1103  /**
1104   * Build HTML to play an audio node.
1105   *
1106   * @param $node
1107   *   Node object.
1108   * @param $playername
1109   *   Optional, string specifying the name of the desired player.
1110   * @return
1111   *   HTML to play the audio tracks.
1112   */
1113  function audio_get_node_player($node, $playername = NULL) {
1114    if (_audio_allow_play($node)) {
1115      $format = $node->audio['format'];
1116      if (!isset($playername)) {
1117        $playername = variable_get('audio_player_'. $format, '1pixelout');
1118      }
1119      $player = audio_get_players('name', $playername);
1120      // Try to use the requested player, if that doesn't work out, use the generic one.
1121      return theme(array($player['theme_node'], 'audio_default_node_player'), $node, isset($player['options']) ? $player['options'] : array());
1122    }
1123  }
1124  
1125  /**
1126   * Provide a link to the audio file.
1127   *
1128   * @param $node
1129   *   Node object.
1130   */
1131  function theme_audio_default_node_player($node) {
1132    return '<span class="audio">'. l(t('Click to play'), $node->url_play) ."</span>\n";
1133  }
1134  
1135  /**
1136   * Take a tag and force it to be a-z 0-9 _ -
1137   *
1138   * @param $string
1139   *   ID3 tag value
1140   * @return
1141   *   cleaned up tag value for URL or database
1142   */
1143  function audio_clean_tag($string) {
1144    // If we've got characters besides 0-9 A-Z a-z hyphen and underscore, replace
1145    // them.
1146    if (preg_match('/[^-\w]/', $string)) {
1147      // Remove accents...
1148      $string = strtr($string, '������������������������������������������������������������', 'SZszYAAAAAACEEEEIIIINOOOOOOUUUUYaaaaaaceeeeiiiinoooooouuuuyy');
1149      // ...convert to equivalent chars...
1150      $string = strtr($string, array('�' => 'TH', '�' => 'th', '�' => 'DH', '�' => 'dh', '�' => 'ss', '�' => 'OE', '�' => 'oe', '�' => 'AE', '�' => 'ae', '�' => 'u'));
1151      // ... and remove anything else that's not alphanumeric and replace it with an underscore.
1152      $string = preg_replace('/[^-\w]+/', '_', $string);
1153    }
1154    // Remove leading and trailing underscores.
1155    $string = trim($string, '_');
1156    // Finally, make it to lower case.
1157    return strtolower($string);
1158  }
1159  
1160  
1161  /**
1162   * Create audio nodes from a file.
1163   *
1164   * Function for other modules to use to create a audio node from a file. Once
1165   * you've created it you can make any changes and then save it using
1166   * node_save().
1167   *
1168   * @param $filepath
1169   *   Full path to an audio file. be aware that the file will be moved into
1170   *   drupal's directory.
1171   * @param $title_format
1172   *   An optional, token string for generating the node's title. If nothing is
1173   *   provided the default title format will be used.
1174   * @param $body
1175   *   An optional string to use for the node's body.
1176   * @param $taxonomy
1177   *   An optional array of taxonomy terms to assign to the node if the taxonomy
1178   *   module is installed.
1179   * @param $tags
1180   *   An optional array of metadata to add to the node. These will overwrite
1181   *   any values loaded from the ID3 tags.
1182   * @return
1183   *   A node or FALSE on error.
1184   */
1185  function audio_api_insert($filepath, $title_format = NULL, $body = NULL, $tags = array(), $taxonomy = array()) {
1186    global $user;
1187  
1188    // For node_object_prepare()
1189    module_load_include('inc', 'node', 'node.pages');
1190  
1191    // Check for user permission.
1192    if (!audio_access('create')) {
1193      drupal_access_denied();
1194    }
1195  
1196    // Begin building file object.
1197    $file = new stdClass();
1198    $file->uid = $user->uid;
1199    $file->filepath = $filepath;
1200    $file->filename = basename($file->filepath);
1201    $file->filemime = module_exists('mimedetect') ? mimedetect_mime($file->filepath) : file_get_mimetype($file->filepath);
1202    $file->filesize = filesize($file->filepath);
1203    $file->timestamp = time();
1204    $file->status = FILE_STATUS_TEMPORARY;
1205    drupal_write_record('files', $file);
1206  
1207    $node = new stdClass();
1208    $node->nid = NULL;
1209    $node->type = 'audio';
1210    $node->uid = $user->uid;
1211    $node->name = $user->name;
1212    $node->language = '';
1213    $node->title = '';
1214    $node->title_format = $title_format;
1215    $node->body = $body;
1216  
1217    // Set the node's defaults... (copied this from node and comment.module)
1218    $node_options = variable_get('node_options_'. $node->type, array('status', 'promote'));
1219    $node->status = in_array('status', $node_options);
1220    $node->moderate = in_array('moderate', $node_options);
1221    $node->promote = in_array('promote', $node_options);
1222    if (module_exists('comment')) {
1223      $node->comment = variable_get("comment_$node->type", COMMENT_NODE_READ_WRITE);
1224    }
1225    if (module_exists('taxonomy') && is_array($taxonomy)) {
1226      $node->taxonomy = $taxonomy;
1227    }
1228  
1229    $node->audio_tags = array();
1230    $node->audio_images = array();
1231  
1232    $node->audio = array(
1233      'downloadable' => variable_get('audio_default_downloadable', 1),
1234      'play_count' => 0,
1235      'download_count' => 0,
1236      'file' => $file,
1237    );
1238  
1239    node_object_prepare($node);
1240  
1241    // Allow other modules to modify the node (hopefully reading in tags).
1242    audio_invoke_audioapi('upload', $node);
1243  
1244    // Add the tags (overwriting any that audio_getid3 may have loaded).
1245    if (is_array($tags)) {
1246      $node->audio_tags = array_merge($node->audio_tags, $tags);
1247    }
1248  
1249    // Build the title manually (since we don't call node_validate()).
1250    if (empty($node->title_format)) {
1251      $node->title_format = variable_get('audio_default_title_format', '[audio-tag-title-raw] by [audio-tag-artist-raw]');
1252    }
1253    // Flush the token cache, otherwise when importing multiple nodes they'll all
1254    // have the same title.
1255    token_get_values('node', $node, TRUE);
1256    $node->title = token_replace($node->title_format, 'node', $node);
1257  
1258    // Save it.
1259    $node = node_submit($node);
1260    node_save($node);
1261  
1262    return $node;
1263  }
1264  
1265  /**
1266   * Parse an array into a valid urlencoded query string.
1267   *
1268   * This function is a work-around for a drupal_urlencode issue in core.
1269   * See: http://drupal.org/node/158687 for details.
1270   *
1271   * @param $query
1272   *   The array to be processed e.g. $_GET.
1273   * @param $exclude
1274   *   The array filled with keys to be excluded. Use parent[child] to exclude
1275   *   nested items.
1276   * @param $parent
1277   *   Should not be passed, only used in recursive calls.
1278   * @return
1279   *   urlencoded string which can be appended to/as the URL query string.
1280   */
1281  function audio_query_string_encode($query, $exclude = array(), $parent = '') {
1282    $params = array();
1283  
1284    foreach ($query as $key => $value) {
1285      $key = urlencode($key);
1286      if ($parent) {
1287        $key = $parent .'['. $key .']';
1288      }
1289  
1290      if (in_array($key, $exclude)) {
1291        continue;
1292      }
1293  
1294      if (is_array($value)) {
1295        $params[] = audio_query_string_encode($value, $exclude, $key);
1296      }
1297      else {
1298        $params[] = $key .'='. urlencode($value);
1299      }
1300    }
1301  
1302    return implode('&', $params);
1303  }
1304  
1305  /**
1306   * Determines if an audio node can be played by Flash players.
1307   *
1308   * @param $node
1309   *   Node object.
1310   * @return
1311   *   Boolean
1312   */
1313  function audio_is_flash_playable($node) {
1314    // Flash only supports a limited range of sample rates.
1315    switch ($node->audio['sample_rate']) {
1316      case '44100': case '22050': case '11025':
1317        return TRUE;
1318      default:
1319        return FALSE;
1320    }
1321  }
1322  
1323  /**
1324   * Implementation of hook_view_api().
1325   */
1326  function audio_views_api() {
1327    return array(
1328      'api' => 2.0,
1329      'path' => drupal_get_path('module', 'audio') . '/views',
1330    );
1331  }
1332  
1333  /**
1334   * Implementation of the token.module's hook_token_values().
1335   *
1336   * @param $type
1337   *   The current context -- 'node', 'user', 'global', etc.
1338   * @param $object
1339   *   The specific node, user, etc. that should be used as the basis for the
1340   *   replacements.
1341   * @return
1342   *   Array of tokens.
1343   */
1344  function audio_token_values($type, $object = NULL) {
1345    $node = $object;
1346    if ($type == 'node' && $node->type == 'audio') {
1347      // Tags.
1348      foreach (audio_get_tags_allowed() as $tag) {
1349        if (isset($node->audio_tags[$tag])) {
1350          $name = strtr($tag, '_', '-');
1351          $tokens['audio-tag-'. $name] = check_plain($node->audio_tags[$tag]);
1352          $tokens['audio-tag-'. $name .'-raw'] = $node->audio_tags[$tag];
1353        }
1354      }
1355  
1356      // Formatted file info.
1357      $tokens['audio-length'] = theme('audio_format_filelength', $node->audio);
1358      $tokens['audio-format'] = theme('audio_format_fileformat', $node->audio);
1359  
1360      // Raw file info.
1361      $keys = array('format', 'filename', 'filepath', 'filemime', 'file_size', 'sample_rate', 'channel_mode', 'bitrate', 'bitrate_mode', 'playtime');
1362      foreach ($keys as $key) {
1363        if (isset($node->audio[$key])) {
1364          $tokens['audio-'. strtr($key, '_', '-')] = check_plain($node->audio[$key]);
1365        }
1366      }
1367      // Play and download links.
1368      if (isset($node->url_play)) {
1369        $tokens['audio-player'] = audio_get_node_player($node);
1370        $tokens['audio-url-play'] = $node->url_play;
1371      }
1372      if (isset($node->url_download)) {
1373        $tokens['audio-url-download'] = $node->url_download;
1374      }
1375  
1376      return $tokens;
1377    }
1378  }
1379  
1380  /**
1381   * Implementation of the token.module's hook_token_values().
1382   *
1383   * @param $type
1384   *   Indicates the context that token help is being generated for. Unlike
1385   *   hook_token_values however, you should show ALL tokens at the same time if
1386   *   $type is 'all'.
1387   * @return
1388   *   Array of token information.
1389   */
1390  function audio_token_list($type = 'all') {
1391    if ($type == 'node' || $type == 'all') {
1392      // Tags.
1393      foreach (audio_get_tags_allowed() as $tag) {
1394        $name = strtr($tag, '_', '-');
1395        $tokens['node']['audio-tag-'. $name] = t("Audio node @tag tag value.", array('@tag' => $tag));
1396        $tokens['node']['audio-tag-'. $name .'-raw'] = t("Audio node @tag tag value. WARNING - raw user input.", array('@tag' => $tag));
1397      }
1398  
1399      // Formatted file info.
1400      $tokens['node']['audio-length']       = t("Audio node formatted file length information.");
1401      $tokens['node']['audio-format']       = t("Audio node formatted file format information.");
1402  
1403      // Raw file info.
1404      $tokens['node']['audio-sample-rate']  = t("Audio node sample rate, integer e.g. 44100.");
1405      $tokens['node']['audio-channel-mode'] = t("Audio node channels, e.g. mono, stereo.");
1406      $tokens['node']['audio-bitrate']      = t("Audio node bitrate, integer e.g. 19200.");
1407      $tokens['node']['audio-bitrate-mode'] = t("Audio node bitrate encoding mode, e.g. vbr, cbr.");
1408      $tokens['node']['audio-playtime']     = t("Audio node play time, minutes:seconds.");
1409      $tokens['node']['audio-file-format']  = t("Audio node file format, e.g. mp3, ogg");
1410      $tokens['node']['audio-file-name']    = t("Audio node original, uploaded file name.");
1411      $tokens['node']['audio-file-path']    = t("Audio node file path.");
1412      $tokens['node']['audio-file-mime']    = t("Audio node MIME type.");
1413      $tokens['node']['audio-file-size']    = t("Audio node file size, in bytes.");
1414  
1415      // Play and download links.
1416      $tokens['node']['audio-player']       = t("Audio node player.");
1417      $tokens['node']['audio-url-play']     = t("Audio node play URL.");
1418      $tokens['node']['audio-url-download'] = t("Audio node download URL.");
1419      return $tokens;
1420    }
1421  }
1422  
1423  /**
1424  * Implements hook_content_extra_fields()
1425  *
1426  * CCK hook to allow sorting of the audio settings field.
1427  */
1428  function audio_content_extra_fields($type_name) {
1429    if ($type_name == 'audio') {
1430      return array(
1431        'audio' => array(
1432          'label' => t('Audio files'),
1433          'description' => t('Audio uploads for posts'),
1434          'weight' => 1,
1435        ),
1436      );
1437    }
1438  }


Generated: Thu Mar 24 11:18:33 2011 Cross-referenced by PHPXref 0.7