here.', array('!href' => url('admin/build/block'))); } } /** * Implementation of hook_init(). */ function follow_init() { drupal_add_css(drupal_get_path('module', 'follow') .'/follow.css'); } /** * Implementation of hook_menu(). */ function follow_menu() { $items = array(); $items['admin/build/follow'] = array( 'title' => 'Site follow links', 'description' => 'Add sitewide follow links', 'page callback' => 'drupal_get_form', 'page arguments' => array('follow_links_form'), 'access arguments' => array('edit site follow links'), ); $items['user/%/follow'] = array( 'title' => 'My follow links', 'description' => 'edit follow links', 'page callback' => 'drupal_get_form', 'page arguments' => array('follow_links_form', 1), 'access callback' => 'follow_links_user_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, ); // No settings! /* $items['admin/settings/follow'] = array( 'title' => 'Follow', 'description' => 'Administer the follow module', 'page callback' => 'drupal_get_form', 'page arguments' => array('follow_settings_form'), 'access arguments' => array('administer follow'), ); */ return $items; } /** * Access callback for user follow links editing. */ function follow_links_user_access($uid) { return ((($GLOBALS['user']->uid == $uid) && user_access('edit own follow links')) || user_access('edit any user follow links')) && $uid > 0; } /** * Implementation of hook_perm(). */ function follow_perm() { return array('edit own follow links', 'edit site follow links', 'edit any user follow links', 'administer follow', 'change follow link titles'); } /** * Implementation of hook_theme(). */ function follow_theme() { $items = array(); $items['follow_links_form'] = array( 'arguments' => array('form' => array()), ); $items['follow_links'] = array( 'arguments' => array('variables' => array()), ); $items['follow_link'] = array( 'arguments' => array('variables' => array()), ); return $items; } /** * Implementation of hook_preprocess_page(). */ function follow_preprocess_page(&$variables) { // Expose the site follow links as a variable to the page template. if ($links = follow_links_load()) { $args = array( 'links' => $links, 'networks' => follow_networks_load(), ); $variables['follow_site'] = theme('follow_links', $args); } } /** * Implementation of hook_block(). */ function follow_block($op = 'list', $delta = 'site', $edit = array()) { // We pass this function through to the D7 versions to ease the process of // keeping the branches in sync. switch($op) { case 'list': return follow_block_info(); default: $fnc = 'follow_block_'. $op; if (function_exists($fnc)) { return $fnc($delta, $edit); } } } /** * Implementation of hook_block_info(). */ function follow_block_info() { $blocks['site'] = array( 'info' => t('Follow Site'), // We need to cache per role so the edit/configure links display only if user // has access. 'cache' => BLOCK_CACHE_PER_ROLE, ); $blocks['user'] = array( 'info' => t('Follow User'), // Here we need to cache per user so the edit link only shows for the associated // account. 'cache' => BLOCK_CACHE_PER_USER, ); return $blocks; } /** * Implementation of hook_block_configure(). */ function follow_block_configure($delta = '') { switch($delta) { case 'site': $form['follow_title'] = array( '#type' => 'radios', '#title' => t('Default block title'), '#default_value' => variable_get('follow_site_block_title', FOLLOW_NAME), '#options' => array( FOLLOW_NAME => t('Follow @name on', array('@name' => variable_get('site_name', 'Drupal'))), FOLLOW_ME => t('Follow me on'), FOLLOW_US => t('Follow us on'), ), ); $form['follow_user'] = array( '#type' => 'checkbox', '#title' => t('User pages'), '#description' => t('Should this block display on user profile pages?'), '#default_value' => variable_get('follow_site_block_user', TRUE), ); return $form; case 'user': $form['follow_title'] = array( '#type' => 'radios', '#title' => t('Default block title'), '#default_value' => variable_get('follow_user_block_title', FOLLOW_NAME), '#options' => array( FOLLOW_NAME => t('Follow [username] on'), FOLLOW_ME => t('Follow me on'), ), ); return $form; } } /** * Implementation of hook_block_save(). */ function follow_block_save($delta = '', $edit = array()) { variable_set('follow_'. $delta .'_block_title', $edit['follow_title']); if ($delta == 'site') { variable_set('follow_site_block_user', $edit['follow_user']); } } /** * Implementation of hook_block_view(). */ function follow_block_view($delta = '') { switch ($delta) { case 'site': if (($content = _follow_block_content()) && (variable_get('follow_site_block_user', TRUE) || !(arg(0) == 'user' && is_numeric(arg(1))))) { return array( 'subject' => _follow_block_subject(), 'content' => $content, ); } break; case 'user': $uid = arg(1); if (arg(0) == 'user' && is_numeric($uid) && ($content = _follow_block_content($uid))) { return array( 'subject' => _follow_block_subject($uid), 'content' => $content, ); } break; } } /** * Helper function to build the block title. * * @param $uid * The uid of the user account. Defaults to the site form, $uid = 0. */ function _follow_block_subject($uid = 0) { return follow_link_title($uid) .':'; } /** * Helper function to create a link or block title. * * @param $uid * The uid of the user account. Defaults to the site form, $uid = 0. */ function follow_link_title($uid = 0) { // Check to see if we have a valid username. if ($uid) { $setting = variable_get('follow_user_block_title', FOLLOW_NAME); // Special handling for usernames. if ($setting == FOLLOW_NAME) { $account = user_load($uid); // Set plain to TRUE for realname module support. return t('Follow !name on', array('!name' => theme('username', array('account' => $account)))); } return t('Follow me on'); } switch(variable_get('follow_site_block_title', FOLLOW_NAME)) { case FOLLOW_NAME: return t('Follow @name on', array('@name' => variable_get('site_name', 'Drupal'))); case FOLLOW_ME: return t('Follow me on'); case FOLLOW_US: return t('Follow us on'); } } /** * Default tooltip callback function to format a tooltip for a follow link. * * The tooltip is the title attribute on the anchor. By default it will display * as "Follow us on Twitter" or similar, depending on the network and your * settings, but networks can define custom tooltip callbacks to change this * text. For instance, an RSS feed or newsletter may want the tooltip to read * "Subscribe to our Feed" or "Subscribe to our Newsletter". * * @param $title * The translated string title of this link * @param $variables * An array containing the link object and network data. * @return * A translated string for the tooltip. */ function follow_link_tooltip($title, $variables) { $link = $variables['link']; // No need to run through t() since both of these already have. We do need to // strip tags however, since theme_username might have output a link. return strip_tags(follow_link_title($link->uid) .' '. $title); } /** * Tooltip for the 'This site' or RSS link. */ function follow_link_tooltip_rss($title, $variables) { return t('Subscribe to feed: @title', array('@title' => $title)); } /** * Tooltip for the Newsletter. */ function follow_link_tooltip_newsletter($title, $variables) { $string = ($title == t('Newsletter')) ? 'Subscribe to @title' : 'Subscribe to newsletter: @title'; return t($string, array('@title' => $title)); } /** * Helper function to build the block content display. * * @param $uid * The uid of the user account. Defaults to the site form, $uid = 0. */ function _follow_block_content($uid = 0) { $output = ''; if ($links = follow_links_load($uid)) { $output = theme('follow_links', array('links' => $links, 'networks' => follow_networks_load($uid))); $output .= _follow_block_config_links($uid); } return $output; } /** * Theme function to output a list of links. * * @param $variables * An array of follow link objects. * @param $networks * An array of network names, keys are machine names, values are visible titles. * * @ingroup themeable */ function theme_follow_links($variables) { $links = $variables['links']; $networks = $variables['networks']; $output = ''; return $output; } /** * Theme function to print an individual link. * * @param $link * A follow link object. * @param $title * The translated title of the social network. * * @ingroup themable */ function theme_follow_link($variables) { $link = $variables['link']; $network = $variables['network']; $title = !empty($link->title) ? $link->title : $network['title']; // @see follow_link_tooltip(). $tooltip_callback = (isset($network['tooltip callback']) && function_exists($network['tooltip callback'])) ? $network['tooltip callback'] : 'follow_link_tooltip'; $tooltip = $tooltip_callback($title, $variables); $classes = array(); $classes[] = 'follow-link'; $classes[] = "follow-link-{$link->name}"; $classes[] = $link->uid ? 'follow-link-user' : 'follow-link-site'; $attributes = array( 'class' => implode(' ', $classes), 'title' => $tooltip, ); $link->options['attributes'] = $attributes; return l($title, $link->path, $link->options) . "\n"; } /** * Outputs a list of configuration links for users with appropriate permissions * * @param $uid * The uid of the user account. Defaults to the site form, $uid = 0. * @return * A string containing the links, output with theme_links(). */ function _follow_block_config_links($uid) { $links = array(); if ($uid == 0 && user_access('edit site follow links')) { $links['follow_edit'] = array( 'title' => t('Edit'), 'href' => 'admin/build/follow', 'query' => drupal_get_destination(), ); } elseif (follow_links_user_access($uid)) { $links['follow_edit'] = array( 'title' => t('Edit'), 'href' => 'user/'. $uid .'/follow', 'query' => drupal_get_destination(), ); } if (user_access('administer blocks')) { $links['follow_configure'] = array( 'title' => t('Configure'), 'href' => $uid ? 'admin/build/block/configure/follow/user' : 'admin/build/block/configure/follow/site', 'query' => drupal_get_destination(), ); } return theme('links', $links, array('class' => 'links inline')); } /** * The form for editing follow links. * * @param $form_state * A keyed array containing the current state of the form. * @param $uid * The uid of the user account. Defaults to the site form, $uid = 0. * * @ingroup forms */ function follow_links_form(&$form_state, $uid = 0) { $form = array(); $form['uid'] = array('#type' => 'hidden', '#value' => $uid); $form['follow_links']['#tree'] = TRUE; $links = follow_links_load($uid); $networks = follow_networks_load($uid, TRUE); // Put all our existing links at the top, sorted by weight. if (is_array($links)) { foreach ($links as $name => $link) { $title = $networks[$name]['title']; $form['follow_links'][$name] = _follow_links_form_link($link, $title, $uid); // Unset this specific network so we don't add the same one again below. unset($networks[$name]); } } // Now add all the empty ones. foreach ($networks as $name => $info) { $link = new stdClass(); $link->name = $name; $form['follow_links'][$name] = _follow_links_form_link($link, $info['title'], $uid); } $form['submit'] = array('#type' => 'submit', '#value' => t('Submit')); return $form; } /** * Helper function to create an individual link form element. */ function _follow_links_form_link($link, $title, $uid) { $elements = array(); $elements['name'] = array( '#type' => 'markup', '#value' => $title, ); if (isset($link->lid)) { $elements['lid'] = array( '#type' => 'hidden', '#value' => $link->lid, ); $elements['weight'] = array( '#type' => 'weight', '#default_value' => $link->weight, ); } $elements['url'] = array( '#type' => 'textfield', '#follow_network' => $link->name, '#follow_uid' => $uid, '#default_value' => isset($link->url) ? $link->url : '', '#element_validate' => array('follow_url_validate'), ); // Provide the title of the link only if the link URL is there and the user // has the appropriate access. $elements['title'] = array( '#type' => 'textfield', '#default_value' => isset($link->title) ? $link->title : '', '#size' => 15, '#access' => user_access('change follow link titles') && !empty($link->url), ); return $elements; } /** * Like drupal_http_build_query() but without urlencodings. */ function follow_build_query(array $query, $parent = '') { $params = array(); foreach ($query as $key => $value) { $key = ($parent ? $parent . '[' . $key . ']' : $key); // Recurse into children. if (is_array($value)) { $params[] = follow_build_query($value, $key); } // If a query parameter value is NULL, only append its key. elseif (!isset($value)) { $params[] = $key; } else { $params[] = $key . '=' . $value; } } return implode('&', $params); } /** * Build a url for use in the form. */ function follow_build_url($path, $options) { $url = $path; if (!empty($options['query'])) { $url .= (strpos($path, '?') !== FALSE ? '&' : '?') . follow_build_query($options['query']); } if (!empty($options['fragment'])) { $url .= '#' . $options['fragment']; } return $url; } /** * Split a Drupal path or external link into path and options like a menu link. */ function follow_parse_url($url) { $parsed_url = parse_url($url); $defaults = array( 'scheme' => '', 'host' => '', 'port' => '', 'path' => '/', 'query' => '', 'fragment' => '', ); $parsed_url += $defaults; $options = array('query' => array(), 'fragment' => $parsed_url['fragment']); // Parse the query string into an array. parse_str($parsed_url['query'], $options['query']); if ($parsed_url['scheme']) { $parsed_url['scheme'] .= '://'; } // Throw away port for now. $path = $parsed_url['scheme'] . $parsed_url['host'] . $parsed_url['path']; return array('path' => $path, 'options' => $options); } /** * Validates the url field to verify it's actually a url. */ function follow_url_validate($form) { $url = trim($form['#value']); $networks = follow_networks_load($form['#follow_uid']); $info = $networks[$form['#follow_network']]; // @see follow_network_regex(). $regex_callback = (isset($info['regex callback']) && function_exists($info['regex callback'])) ? $info['regex callback'] : 'follow_network_regex'; $regex = $regex_callback($info); $parsed = follow_parse_url($url); if($url && !preg_match($regex, $parsed['path'])) { if (!empty($info['domain'])) { $message = t('The specified url is invalid for the domain %domain. Make sure you use http://.', array('%domain' => $info['domain'])); } else { $message = t('The specified path is invalid.'); } form_error($form, $message); } } /** * The default regex callback for a follow network. This simply returns a regular * expression to be used in validating the saved url. * * @param $network_info * An array of data about this network. * @return * A string, regular expression used to validate the submitted url. */ function follow_network_regex($network_info) { // An external link. return '@^https?://([a-z0-9\-_.]+\\.|)' . str_replace('.', '\\.', $network_info['domain']) . '/@i'; } /** * Verify that a url is relative. */ function follow_network_regex_internal() { // An internal link should not have ':'. return '@^[^:]+$@'; } /** * Verify that a url isÉ a url. */ function follow_network_regex_external() { // @see http://j.mp/2Ol4gj return ' /^(https?|ftp):\/\/(?# protocol )(([a-z0-9$_\.\+!\*\'\(\),;\?&=-]|%[0-9a-f]{2})+(?# username )(:([a-z0-9$_\.\+!\*\'\(\),;\?&=-]|%[0-9a-f]{2})+)?(?# password )@)?(?# auth requires @ )((([a-z0-9][a-z0-9-]*[a-z0-9]\.)*(?# domain segments AND )[a-z]{2}[a-z0-9-]*[a-z0-9](?# top level domain OR )|(\d|[1-9]\d|1\d{2}|2[0-4][0-9]|25[0-5]\.){3}(?# )(\d|[1-9]\d|1\d{2}|2[0-4][0-9]|25[0-5])(?# IP address ))(:\d+)?(?# port ))(((\/+([a-z0-9$_\.\+!\*\'\(\),;:@&=-]|%[0-9a-f]{2})*)*(?# path )(\?([a-z0-9$_\.\+!\*\'\(\),;:@&=-]|%[0-9a-f]{2})*)(?# query string )?)?)?(?# path and query string optional )(#([a-z0-9$_\.\+!\*\'\(\),;:@&=-]|%[0-9a-f]{2})*)?(?# fragment )$/i'; } /** * Submit handler for the follow_links_form. */ function follow_links_form_submit($form, &$form_state) { $values = $form_state['values']; $links = $values['follow_links']; foreach($links as $name => $link) { $link = (object) $link; $link->url = trim($link->url); // Check to see if there's actually a link if (empty($link->url)) { // If there's an lid, delete the link. if (isset($link->lid)) { follow_link_delete($link->lid); } // Continue to the next link. continue; } // Otherwise save the link. else { $link->uid = $values['uid']; $link->name = $name; follow_link_save($link); } } } /** * Theme the drag-and-drop form. * * Arranges records in a table, and adds the css and js for draggable sorting. * * @ingroup themeable * @ingroup forms */ function theme_follow_links_form($form) { $rows = array(); $disabled_rows = array(); foreach (element_children($form['follow_links']) as $key) { $row = array(); if (isset($form['follow_links'][$key]['weight'])) { $row[] = drupal_render($form['follow_links'][$key]['lid']) . drupal_render($form['follow_links'][$key]['name']); $row[] = drupal_render($form['follow_links'][$key]['url']); if (user_access('change follow link titles')) { $row[] = drupal_render($form['follow_links'][$key]['title']); } // Now, render the weight row. $form['follow_links'][$key]['weight']['#attributes']['class'] = 'follow-links-weight'; $row[] = drupal_render($form['follow_links'][$key]['weight']); // Add the new row to our collection of rows, and give it the 'draggable' class. $rows[] = array( 'data' => $row, 'class' => 'draggable', ); } else { $disabled_rows[] = array(drupal_render($form['follow_links'][$key]['name']), drupal_render($form['follow_links'][$key]['url'])); } } // Render a list of header titles, and our array of rows, into a table. $header = array(t('Name'), t('URL')); if (user_access('change follow link titles')) { $header[] = t('Customized Name'); } $header[] = t('Weight'); $disabled_header = array(t('Name'), t('URL')); $output = ''; if (count($rows)) { $output .= theme('table', $header, $rows, array('id' => 'follow-links-weighted-form')); } if (count($disabled_rows)) { $output .= theme('table', $disabled_header, $disabled_rows); } $output .= drupal_render($form); drupal_add_tabledrag('follow-links-weighted-form', 'order', 'self', 'follow-links-weight'); return $output; } /** * Follow settings form. * * @ingroup forms */ function follow_settings_form() { $form = array(); return system_settings_form($form); } /** * Loader function for individual links. * * @param $uid * An int containing the uid of the user. uid 0 pulls the site follow links. * @return * A single link in array format, or FALSE if none matched the incoming ID. */ function follow_links_load($uid = 0) { $links = array(); $sql = "SELECT * FROM {follow_links} WHERE uid = %d ORDER BY weight ASC"; $result = db_query($sql, $uid); while ($link = db_fetch_object($result)) { $link->options = unserialize($link->options); $link->url = follow_build_url($link->path, $link->options); $links[$link->name] = $link; } return $links; } /** * Inserts a new link, or updates an existing one. * * @param $link * A link object to be saved. */ function follow_link_save($link) { $parsed = follow_parse_url($link->url); $link->path = $parsed['path']; $link->options = $parsed['options']; if (isset($link->lid)) { drupal_write_record('follow_links', $link, 'lid'); } else { drupal_write_record('follow_links', $link); } return $link; } /** * Deletes a link, given its unique ID. * * @param $lid * An int containing the ID of a link. */ function follow_link_delete($lid) { $sql = 'DELETE FROM {follow_links} WHERE lid = %d'; db_query($sql, $lid); } /** * Loads all follow networks * * @param $uid * The uid of the user. uid 0 pulls the site follow links. * @param $reset * Boolean. If TRUE, flushes the follow networks cache. * * @return * An array of network names, keys are machine names, values are visible titles. */ function follow_networks_load($uid = 0, $reset = FALSE) { static $networks = array(); // Clear cache if $reset is TRUE; if ($reset) { $networks = array(); } // Return presets if the array is populated. if (empty($networks[$uid])) { // We call hook_follow_networks_alter() to allow other modules to create // or alter networks. $networks[$uid] = follow_default_networks($uid); drupal_alter('follow_networks', $networks, $uid); } return $networks[$uid]; } /** * Retrieves the default networks available. * * @return * An associative array, keyed by the machine name. The values are an array * including title of the network, along with the domain to be used for * input validation of the network. */ function follow_default_networks($uid) { $networks = array( 'facebook' => array( 'title' => t('Facebook'), 'domain' => 'facebook.com', ), 'virb' => array( 'title' => t('Virb'), 'domain' => 'virb.com', ), 'myspace' => array( 'title' => t('MySpace'), 'domain' => 'myspace.com', ), 'twitter' => array( 'title' => t('Twitter'), 'domain' => 'twitter.com', ), 'picasa' => array( 'title' => t('Picasa'), 'domain' => 'picasaweb.google.com', ), 'flickr' => array( 'title' => t('Flickr'), 'domain' => 'flickr.com', ), 'youtube' => array( 'title' => t('YouTube'), 'domain' => 'youtube.com', ), 'vimeo' => array( 'title' => t('Vimeo'), 'domain' => 'vimeo.com', ), 'bliptv' => array( 'title' => t('blip.tv'), 'domain' => 'blip.tv', ), 'lastfm' => array( 'title' => t('last.fm'), 'domain' => 'last.fm', ), 'linkedin' => array( 'title' => t('LinkedIn'), 'domain' => 'linkedin.com', ), 'delicious' => array( 'title' => t('Delicious'), 'domain' => 'delicious.com', ), 'tumblr' => array( 'title' => t('Tumblr'), 'domain' => 'tumblr.com', ), ); if ($uid == 0) { $networks['this-site'] = array( 'title' => t('This site (RSS)'), 'domain' => '', 'regex callback' => 'follow_network_regex_internal', 'tooltip callback' => 'follow_link_tooltip_rss', ); $networks['newsletter'] = array( 'title' => t('Newsletter'), 'domain' => '', 'regex callback' => 'follow_network_regex_external', 'tooltip callback' => 'follow_link_tooltip_newsletter', ); } return $networks; }