'Playlist', 'page callback' => 'drupal_get_form', 'page arguments' => array('station_playlist_admin_settings'), 'access arguments' => array('administer site configuration'), 'file' => 'station_playlist.admin.inc', 'type' => MENU_LOCAL_TASK ); $items['station/autocomplete/playlist'] = array( 'title' => 'Playlist Autocomplete', 'page callback' => 'station_playlist_autocomplete', 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); $items['station/ahah/playlist'] = array( 'page callback' => 'station_playlist_ahah', 'page arguments' => array(), 'access arguments' => array('access content'), 'type' => MENU_CALLBACK, ); return $items; } /** * Implementation of hook_theme(). */ function station_playlist_theme() { return array( 'station_playlist_track_form' => array( 'arguments' => array('form' => NULL), ), ); } /** * Implementation of hook_node_info(). */ function station_playlist_node_info() { return array( 'station_playlist' => array( 'name' => t('Program Playlist'), 'module' => 'station_playlist', 'description' => t("A playlist lets your listeners see a list of all the music played during a program. It also allows the music director to easily chart what's being played."), 'has_title' => TRUE, 'title_label' => t('Title'), 'has_body' => TRUE, 'body_label' => t('Description'), ) ); } /** * Implementation of hook_perm(). */ function station_playlist_perm() { return array( 'administer station playlists', 'view station playlist content', 'create station playlist content', 'edit any station playlist content', 'edit own station playlist content', 'delete any station playlist content', 'delete own station playlist content', ); } /** * Implementation of hook_link(). * * This is implemented so that an edit link is displayed for users who have * the rights to edit a node. */ function station_playlist_link($type, $object, $teaser = FALSE) { if ($type == 'node') { switch ($object->type) { case 'station_program': $links = array(); $field = content_fields('field_station_program', 'station_playlist'); $db_info = content_database_info($field); $col = $db_info['columns']['nid']['column']; $result = db_query(db_rewrite_sql("SELECT COUNT(n.nid) FROM {node} n INNER JOIN {{$db_info['table']}} sp ON n.nid = sp.$col WHERE sp.$col = %d AND n.status = 1"), $object->nid); if (db_result($result)) { $links['station_station_playlist_view'] = array( 'title' => t('View all playlists'), 'href' => 'station/playlists/'. $object->nid, ); } if (!$teaser) { if (node_access('update', $object)) { $links['station_station_playlist_add'] = array( 'title' => t('Add new playlist'), 'href' => 'node/add/station-playlist/'. $object->nid, ); } } return $links; case 'station_playlist': if (!empty($object->field_station_program[0]['nid'])) { return array('station_program_program_view' => array( 'title' => t('View program'), 'href' => 'node/'. $object->field_station_program[0]['nid'], )); } } } return array(); } /** * Implementation of hook_access(). */ function station_playlist_access($op, $node, $account) { if (user_access('administer station playlists', $account)) { return TRUE; } switch ($op) { case 'view': return user_access('view station playlist content', $account); case 'create': return user_access('create station playlist content', $account); case 'update': if (user_access('edit own station playlist content', $account) && ($account->uid == $node->uid)) { return TRUE; } return user_access('edit any station playlist content', $account); case 'delete': if (user_access('delete own station playlist content', $account) && ($account->uid == $node->uid)) { return TRUE; } return user_access('delete any station playlist content', $account); } } /** * Does this track have all the required fields be saved? * * Tracks need to have an artist or title to be saved to the database. * * @param $track * Array with track information. * @return * The track has some values filled in but is missing an artist or title. * @see station_playlist_track_is_incomplete() */ function station_playlist_track_is_saveable($track) { return ($track['artist'] || $track['title']); } /** * Does an unsavable track have other values set? * * @param $track * Array with track information. * @return * The track has some values filled in but is missing an artist or title. * @see station_playlist_track_is_empty() */ function station_playlist_track_is_incomplete($track) { return ($track['album'] || $track['label'] || $track['link']) && !($track['artist'] || $track['title']); } /** * Helper function to build an empty track row. */ function station_playlist_get_empty_track() { static $track; if (!isset($track)) { $track = array( 'artist' => '', 'album' => '', 'title' => '', 'label' => '', 'link' => '', 'weight' => 9999, ); drupal_alter('station_playlist_empty_track', $track); } return $track; } /** * Implementation of hook_form(). * * Build a form for playlist nodes. */ function station_playlist_form(&$node, $form_state) { // if this is a new node (with no nid) and we've got a numeric argument, // assume that's the program we should attach to. if (empty($node->nid) && is_numeric(arg(3))) { $node->field_station_program[0]['nid'] = (int) arg(3); } // Figure out how many empty rows we have and decide if we need to add some // more. if (isset($node->tracks)) { $num_empty_tracks = 5; foreach ($node->tracks as $track) { if (!station_playlist_track_is_saveable($track)) { $num_empty_tracks--; } } } else { // New playlists get 10 blank tracks. $num_empty_tracks = 10; $node->tracks = array(); } $empty_track = station_playlist_get_empty_track(); for ($i = 0; $i < $num_empty_tracks; $i++) { $node->tracks[] = $empty_track; } $form['body_filter']['body'] = array( '#type' => 'textarea', '#title' => t('Description'), '#default_value' => $node->body, '#rows' => 5, ); $form['body_filter']['format'] = filter_form($node->format); // Wrapper to hold all the tracks elements. $form['tracks_wrapper'] = array( '#value' => '', '#prefix' => '
', '#suffix' => '
', '#theme' => 'station_playlist_track_form', '#description' => t('Enter the tracks played on the show, the artist and title are required. If you provide a link, it needs to begin with http:// or you will get a link to a page on this site.'), '#tree' => FALSE, ); // Container to display existing tracks. $form['tracks_wrapper']['tracks'] = array( '#prefix' => '
', '#suffix' => '
', '#tree' => TRUE, ); foreach ($node->tracks as $weight => $track) { $form['tracks_wrapper']['tracks'][$weight]['artist'] = array( '#type' => 'textfield', '#size' => 20, '#maxlength' => 255, '#default_value' => isset($track['artist']) ? $track['artist'] : '', '#autocomplete_path' => 'station/autocomplete/playlist/artist', ); $form['tracks_wrapper']['tracks'][$weight]['album'] = array( '#type' => 'textfield', '#size' => 20, '#maxlength' => 255, '#default_value' => isset($track['album']) ? $track['album'] : '', '#autocomplete_path' => 'station/autocomplete/playlist/album', ); $form['tracks_wrapper']['tracks'][$weight]['title'] = array( '#type' => 'textfield', '#size' => 20, '#maxlength' => 255, '#default_value' => isset($track['title']) ? $track['title'] : '', ); $form['tracks_wrapper']['tracks'][$weight]['label'] = array( '#type' => 'textfield', '#size' => 20, '#maxlength' => 255, '#default_value' => isset($track['label']) ? $track['label'] : '', '#autocomplete_path' => 'station/autocomplete/playlist/label', ); $form['tracks_wrapper']['tracks'][$weight]['link'] = array( '#type' => 'textfield', '#size' => 20, '#maxlength' => 255, '#default_value' => isset($track['link']) ? $track['link'] : '', ); $form['tracks_wrapper']['tracks'][$weight]['weight'] = array( '#type' => 'weight', '#default_value' => $weight, '#delta' => 100, ); // Remove button. $form['tracks_wrapper']['tracks'][$weight]['remove'] = array( '#type' => 'submit', '#name' => 'remove_' . $weight, '#value' => t('Remove'), '#submit' => array('station_playlist_form_ahah_track_submit'), '#ahah' => array( 'path' => 'station/ahah/playlist', 'wrapper' => 'station-playlist-tracks-wrapper', 'method' => 'replace', 'effect' => 'fade', ), ); } // We name our button 'add_more' to avoid conflicts with other modules using // AHAH-enabled buttons with the id 'more'. $form['tracks_wrapper']['add_more'] = array( '#type' => 'submit', '#value' => t('Add more'), '#weight' => 1, '#submit' => array('station_playlist_form_ahah_track_submit'), '#ahah' => array( 'path' => 'station/ahah/playlist', 'wrapper' => 'station-playlist-tracks-wrapper', 'method' => 'replace', 'effect' => 'fade', ), ); $form['#validate'][] = 'station_playlist_node_form_validate'; $form['#submit'][] = 'station_playlist_node_form_submit'; return $form; } /** * A do nothing submit function to prevent the form from saving. */ function station_playlist_form_ahah_track_submit($form, &$form_state) { // Set the form to rebuild and run submit handlers. node_form_submit_build_node($form, $form_state); } function theme_station_playlist_track_form($form) { // To have the drag and drop not totally wack out the formatting we need // the first column in the table with no form element. $header = array('', t('Artist'), t('Title'), t('Album'), t('Label'), t('Link'), t('Weight'), ''); $rows = array(); foreach (element_children($form['tracks']) as $key) { $form['tracks'][$key]['weight']['#attributes']['class'] = 'track-weight'; $row = array( '', drupal_render($form['tracks'][$key]['artist']), drupal_render($form['tracks'][$key]['title']), drupal_render($form['tracks'][$key]['album']), drupal_render($form['tracks'][$key]['label']), drupal_render($form['tracks'][$key]['link']), drupal_render($form['tracks'][$key]['weight']), drupal_render($form['tracks'][$key]['remove']), ); $rows[] = array('data' => $row, 'class' => 'draggable'); } $output = ''; if (count($rows)) { drupal_add_tabledrag('station-playlist-tracks-table', 'order', 'sibling', 'track-weight'); $output .= theme('table', $header, $rows, array('id' => 'station-playlist-tracks-table')); } return $output . drupal_render($form); } function station_playlist_node_form_validate($form, &$form_state) { $tracks = $form_state['values']['tracks']; // Check if a track should be removed. if (isset($form_state['clicked_button']['#parents'][2]) && $form_state['clicked_button']['#parents'][2] == 'remove') { $delta = $form_state['clicked_button']['#parents'][1]; unset($tracks[$delta]); } // Sort the table by weight (which might have changed with a tablesort) // then reset the keys. usort($tracks, '_station_playlist_sort_tracks'); $tracks = array_values($tracks); // Now strip the previous weights which may not be adjacent and update the // track's weights. foreach ($tracks as $key => $track) { $track['weight'] = $key; } // Check if they've requested a new track. if (isset($form_state['clicked_button']['#parents'][0]) && $form_state['clicked_button']['#parents'][0] == 'add_more') { // Add the new row to the tracks list. $track = station_playlist_get_empty_track(); for ($i = 0; $i < 5; $i++) { $tracks[] = $track; } } form_set_value($form['tracks_wrapper']['tracks'], $tracks, $form_state); } /** * Helper function to sort tracks by weight. */ function _station_playlist_sort_tracks($a, $b) { if ((int) $a['weight'] == (int) $b['weight']) { return 0; } return ((int) $a['weight'] < (int) $b['weight']) ? -1 : 1; } /** * Implementation of hook_validate(). * * Use this hook to convert form elements to node values. */ function station_playlist_validate(&$node, &$form) { // Check for missing track information. foreach ($node->tracks as $track) { if (station_playlist_track_is_incomplete($track)) { form_set_error('tracks]['. $track['weight'] .'][artist', t("You must provide either an artist or a title.")); form_set_error('tracks]['. $track['weight'] .'][title', ' '); } } } function station_playlist_ahah() { // The form is generated in an include file which we need to include manually. include_once 'modules/node/node.pages.inc'; $form = station_ajax_form_handler(); // Render the new output. $sub_form = $form['tracks_wrapper']; // Prevent duplicate wrappers. unset($sub_form['#prefix'], $sub_form['#suffix']); $output = theme('status_messages') . drupal_render($sub_form); // AHAH is not being nice to us and doesn't know about the "Remove" button. // This causes it not to attach AHAH behaviours to it after modifying the form. // So we need to tell it first. $javascript = drupal_add_js(NULL, NULL); if (isset($javascript['setting'])) { $output .= ''; } // Final rendering callback. drupal_json(array('status' => TRUE, 'data' => $output)); } /** * Form submit handler to set the node's title(). */ function station_playlist_node_form_submit($form, &$form_state) { // Compute the title. $form_state['values']['title'] = t('!program-title playlist for !date', array( '!program-title' => _nodereference_titles($form_state['values']['field_station_program'][0]['nid']), '!date' => format_date($form_state['values']['field_station_playlist_date'][0]['value'], 'custom', variable_get('station_playlist_title_dateformat', 'm/d/Y')), )); } /** * Implementation of hook_load(). */ function station_playlist_load($node) { // Tracks $extras = new stdClass(); $extras->tracks = array(); $result = db_query('SELECT * FROM {station_playlist_track} WHERE nid = %d ORDER BY weight', $node->nid); while ($track = db_fetch_array($result)) { $extras->tracks[] = $track; } return $extras; } /** * Insert a new playlist */ function station_playlist_insert($node) { foreach ($node->tracks as $track) { if (station_playlist_track_is_saveable($track)) { $record = array_merge($track, array('nid' => $node->nid, 'vid' => $node->vid)); drupal_write_record('station_playlist_track', $record); } } } /** * Delete a playlist. */ function station_playlist_delete($node) { db_query('DELETE FROM {station_playlist_track} WHERE nid = %d', $node->nid); } /** * Update a playlist. */ function station_playlist_update($node) { // delete and re-add tracks db_query('DELETE FROM {station_playlist_track} WHERE nid = %d', $node->nid); foreach ($node->tracks as $track) { if (station_playlist_track_is_saveable($track)) { $record = array_merge($track, array('nid' => $node->nid, 'vid' => $node->vid)); drupal_write_record('station_playlist_track', $record); } } } /** * Get a playlist ready for viewing. */ function station_playlist_view(&$node, $teaser = false, $page = false) { $node = node_prepare($node, $teaser); if ($page) { $program_nid = $node->field_station_program[0]['nid']; $breadcrumb = drupal_get_breadcrumb(); $breadcrumb[] = l(t('Station'), 'station'); $breadcrumb[] = l(t('Programs'), 'station/programs'); $breadcrumb[] = l(_nodereference_titles($program_nid), 'node/'. $program_nid); drupal_set_breadcrumb($breadcrumb); } if (!$teaser && isset($node->nid)) { if ($view = views_get_view('station_playlist_tracks')) { $display_id = 'default'; if ($view->access($display_id)) { if ($output = $view->preview($display_id, array($node->nid))) { $node->content['tracks'] = array( '#value' => $output, '#title' => 'Tracks', '#weight' => 0, ); } } $view->destroy(); } } return $node; } /** * Implementation of hook_nodeapi(). * * We inject our past and future playlists onto program nodes here. */ function station_playlist_nodeapi(&$node, $op, $teaser, $page) { if ($node->type == 'station_program' && $op == 'view') { $block_content_mapping = array( 'block_1' => 'playlists_future', 'block_2' => 'playlists_past', ); foreach ($block_content_mapping as $display_id => $content_key) { if ($view = views_get_view('station_program_playlists')) { if ($view->access($display_id)) { $block = $view->execute_display($display_id); if (isset($block)) { $node->content[$content_key]['#type'] = 'item'; $node->content[$content_key]['#title'] = $block['subject']; $node->content[$content_key]['#value'] = $block['content']; } } $view->destroy(); } } } } /** * Retrieve a list of autocomplete suggestions for playlist items. * * @param $field * The name of the field to match: 'artist', 'album', 'label' * @param $string * The value to search for. */ function station_playlist_autocomplete($field = '', $string = '') { $matches = array(); if (in_array($field, array('artist', 'album', 'label'))) { $sql = NULL; $catalog_sql = 'SELECT DISTINCT %s AS val FROM {station_catalog} WHERE LOWER(%s) LIKE LOWER("%s%%")'; $playlist_sql = 'SELECT DISTINCT %s AS val FROM {station_playlist_track} WHERE LOWER(%s) LIKE LOWER("%s%%")'; $args = array($field, $field, $string); switch (variable_get('station_playlist_track_autocomplete_source', 'playlists')) { case 'playlists': $sql = $playlist_sql; break; case 'catalog': $sql = $catalog_sql; break; case 'both': $sql = $catalog_sql . ' UNION ' . $playlist_sql; $args = array_merge($args, $args); break; } if ($sql) { $result = db_query_range($sql . ' ORDER BY val', $args, 0, 10); while ($item = db_fetch_object($result)) { $matches[$item->val] = check_plain($item->val); } } } print drupal_to_js($matches); exit(); } /** * Implementation of hook_content_extra_fields. * * Let CCK know about the playlist stuff we're putting on nodes. */ function station_playlist_content_extra_fields($type_name) { switch ($type_name) { case 'station_playlist': return array( 'tracks' => array( 'label' => t('Tracks'), 'description' => t('Station Playlist module form.'), 'weight' => 0, ), ); case 'station_program': return array( 'playlists_future' => array( 'label' => t('Upcoming shows'), 'description' => t('Station Playlist module form.'), 'weight' => 1, ), 'playlists_past' => array( 'label' => t('Previous shows'), 'description' => t('Station Playlist module form.'), 'weight' => 2, ), ); } } /** * Implementation of hook_view_api(). */ function station_playlist_views_api() { return array( 'api' => 2.0, 'path' => drupal_get_path('module', 'station_playlist'), ); }