uid; } } $link['href'] = implode('/', $path_parts); } } /** * Helper function to keep all the variable gets in one place. * * @param $name * The variable we wish to retrieve. * * @return mixed * The value of the requested variable. */ function me_variable_get($name) { static $defaults = array( 'me_alias' => 'me', 'me_case_insensitive' => FALSE, 'me_redirect' => FALSE, 'me_path_rule' => ME_PATH_EXCLUDE, 'me_paths' => '', 'me_redirect_anonymous' => '', 'me_theme_menu_item_link' => '', 'me_rewrite_link' => TRUE, 'me_user_override' => FALSE, ); return variable_get($name, $defaults[$name]); } /** * Helper function to check if me should handle a given path. * * @param $path * The path to check * * @return boolean * TRUE if the path is handled by the me module. FALSE otherwise. */ function _me_handle_path($path) { // Match path if necessary $paths = me_variable_get('me_paths'); $path_rule = me_variable_get('me_path_rule'); $path_match = TRUE; if (!empty($paths)) { if ($path_rule !== ME_PATH_PHP) { $path = drupal_get_path_alias($_GET['q']); // Compare with the internal and path alias (if any). $path_match = drupal_match_path($path, $paths); if ($path != $_GET['q']) { $path_match = $path_match || drupal_match_path($_GET['q'], $paths); } // When $path_rule has a value of ME_PATH_EXCLUDE, then me works on // all paths except those listed in $paths. When set to ME_PATH_INCLUDE, it // is used only on those pages listed in $paths. $path_match = !($path_rule xor $path_match); } else { $path_match = drupal_eval($paths); } } return $path_match; } /** * A special menu callback function that either redirects to * a page with the uid in the path, or calls the real menu handler. * * @param $parts * The menu parts we are working with. * @param $callback * The page callback to call. * @param ... * count($parts) arguments for each part of the actual path * @param ... * Any extra arguments will be the real page arguments. * * @return mixed * Whatever the real page callback returns. */ function me_handler($parts, $callback) { // Get the arguments, and shift off $parts and $callback. $args = func_get_args(); array_shift($args); array_shift($args); // If we want the uid shown in the address bar, we need to do a redirect. if (me_variable_get('me_redirect') || _me_user_disabled() || !_me_handle_path($_GET['q'])) { $redirect = FALSE; // Get the menu path arguments. $menu_parts = explode('/', $_GET['q'], MENU_MAX_PARTS); // Loop over each part. If it's a %me wildcard, then // check the corresponding menu part for the me alias, // if so, replace it out with the user id so we can redirect correctly. // If no changes are required, then call the required function. while (list($key, $val) = each($parts)) { if (0 === strpos($val, '%me') && _me_is_alias($menu_parts[$key])) { $redirect = TRUE; $menu_parts[$key] = $GLOBALS['user']->uid; } } if ($redirect) { $path = implode('/', $menu_parts); // Save on an extra redirect by also checking the anonymous redirect here. $redirect_path = me_variable_get('me_redirect_anonymous'); if ($GLOBALS['user']->uid == 0 && !empty($redirect_path)) { $path = $redirect_path; } drupal_goto($path); } } // Before going any further, set the current menu router item to include // paths with %user, which allows modules to use menu_get_object() instead // of arg() in blocks and the like. $router_item = menu_get_item(); foreach ($router_item['load_functions'] as $index => $function) { // If the function is a me handled function, then swap the handler out with user. if (0 === strpos($function, 'me')) { $router_item['load_functions'][$index] = 'user_load'; } } menu_set_item($_GET['q'], $router_item); return call_user_func_array($callback, $args); } /** * Helper function to check if a user can have, and has me disabled. * * @return boolean * TRUE if the user has me disabled. FALSE otherwise. */ function _me_user_disabled() { return me_variable_get('me_user_override') && !empty($GLOBALS['user']->me_disable); } /** * Implementation of hook_menu_alter(). */ function me_menu_alter(&$callbacks) { // Loop over each of the paths, finding all %user* loaders, // and replace them with a %me equivelant. This should catch // all drupal modules that use the %user loader to load up // user objects, which should be most well written D6 modules. // Certainly all of core. $processed = array(); //XXX: For now, we only handle known user loaders. I might make a module hook, or a configuration // area to allow these to be exteneded if users make the requests. $handlers = array( '%user' => '%me', '%user_uid_optional' => '%me_uid_optional', '%user_category' => '%me_category', ); foreach ($callbacks as $path => $data) { $found = FALSE; $parts = explode('/', $path, MENU_MAX_PARTS); foreach ($handlers as $user_handler => $me_handler) { if (in_array($user_handler, $parts)) { $found = TRUE; break; } } if ($found) { // We need to make sure that the correct files are loaded up. when the path is used. if (isset($data['file']) && !isset($data['file path'])) { $data['file path'] = drupal_get_path('module', $data['module']); } // We need to find the right page callback and page arguments to make // the me handler work correctly. $new_parts = array(); while (list($key, $val) = each($parts)) { if (array_key_exists($val, $handlers)) { $val = $handlers[$val]; } $new_parts[] = $val; } $new_path = implode('/', $new_parts); // We need to be careful with load arguments due too http://drupal.org/node/373568. // We therefore only add load arguments if there are some there already. // The only load argument that needs to be passed by reference is map. // We make sure that we have map in the right place to be passed by reference. if (is_array($data['load arguments'])) { // Find the current map index, and add our load arguments, putting map // in the place we expect it to be. if (FALSE !== ($map_index = array_search('%map', $data['load arguments']))) { unset($data['load arguments'][$map_index]); array_unshift($data['load arguments'], '%map', '%index', strval($map_index)); } } // First, we need to find the parent. $parent_path = implode('/', array_slice($parts, 0, count($parts) - 1)); if (in_array($parent_path, $processed)) { $parts = explode('/', $new_path, MENU_MAX_PARTS); $parent_path = implode('/', array_slice($parts, 0, count($parts) - 1)); } $parent = $callbacks[$parent_path]; if (!isset($data['page callback']) && isset($parent['page callback'])) { $data['page callback'] = $parent['page callback']; if (!isset($data['page arguments']) && isset($parent['page arguments'])) { $data['page arguments'] = $parent['page arguments']; } if (!isset($data['file']) && isset($parent['file'])) { $data['file'] = $parent['file']; } if (!isset($data['file path']) && isset($parent['file path'])) { $data['file path'] = $parent['file path']; } } if (isset($data['page callback'])) { if (!is_array($data['page arguments'])) { $data['page arguments'] = array(); } $parts = explode('/', $new_path, MENU_MAX_PARTS); $data['page arguments'] = array_merge(array($parts, $data['page callback']), $data['page arguments']); $data['page callback'] = 'me_handler'; } $callbacks[$new_path] = $data; unset($callbacks[$path]); $processed[] = $path; } } } /** * Helper function to set up arguments in meun _load callbacks. */ function _me_load_arguments($uid, &$map = NULL, $index = NULL, $map_index = FALSE, $args = array(), $function = 'user_load') { // We need to get all the arguments, remove our custom ones, // put %map in the right place, then call the menu load callack. array_splice($args, 0, min(4, count($args))); if (!is_null($map) && FALSE !== $map_index) { $insert = array( &$map, ); array_splice($args, $map_index, 0, $insert); $map[$index] = _me_check_arg($uid); } array_unshift($args, _me_check_arg($uid)); // If we have a valid function to call, call it. if (function_exists($function)) { return call_user_func_array($function, $args); } return FALSE; } /** * Menu load callback in place of user_load(). */ function me_load($uid, &$map = NULL, $index = NULL, $map_index = FALSE) { $args = func_get_args(); return _me_load_arguments($uid, $map, $index, $map_index, $args); } /** * Menu load callback in place of user_uid_optional_load(). */ function me_uid_optional_load($uid, &$map = NULL, $index = NULL, $map_index = FALSE) { $args = func_get_args(); return _me_load_arguments((isset($uid) ? $uid : $GLOBALS['user']->uid), $map, $index, $map_index, $args); } /** * Menu load callback in place of user_category_load(). */ function me_category_load($uid, &$map = NULL, $index = NULL, $map_index = FALSE) { $args = func_get_args(); return _me_load_arguments($uid, $map, $index, $map_index, $args, 'user_category_load'); } /** * Menu to_arg function for %me. */ function me_to_arg($arg, $map, $index) { $uid = user_uid_optional_to_arg($arg, $map, $index); if (me_variable_get('me_rewrite_link') && !_me_user_disabled()) { return $uid == $GLOBALS['user']->uid ? _me_get_me_alias() : $uid; } else { return $uid; } } /** * Menu to_arg function for %me_uid_optional. */ function me_uid_optional_to_arg($arg, $map, $index) { return me_to_arg($arg, $map, $index); } /** * Menu to_arg function for %me_category. */ function me_category_to_arg($arg, $map, $index) { return me_to_arg($arg, $map, $index); } /** * A Helper function to check for the 'me' alias. * * @param $arg * The argument to check. * @param $username * If TRUE, will return the username instead of the users id. * @param $redirect * When TRUE, anonymous users will be redirected if a path is available. * * @return mixed * The current user id if a match is found, or the given argument * if no match. */ function _me_check_arg($arg, $username = FALSE, $redirect = TRUE) { $return = _me_is_alias($arg) ? $username ? $GLOBALS['user']->name : $GLOBALS['user']->uid : $arg; $redirect_path = me_variable_get('me_redirect_anonymous'); if ($redirect && $GLOBALS['user']->uid == 0 && !empty($redirect_path)) { // Copied from menu_get_item(). We can't call that here as it might cause a recursion loop. $original_map = arg(NULL, $_GET['q']); $parts = array_slice($original_map, 0, MENU_MAX_PARTS); list($ancestors, $placeholders) = menu_get_ancestors($parts); if (($router_item = db_fetch_array(db_query_range('SELECT * FROM {menu_router} WHERE path IN ('. implode (',', $placeholders) .') ORDER BY fit DESC', $ancestors, 0, 1))) && $router_item['page_callback'] == 'me_handler') { // Not unsetting the destination can cause evil redirect loops. unset($_REQUEST['destination'], $_REQUEST['edit']['destination']); drupal_goto($redirect_path); } } return $return; } /** * Helper function to return the me alias. * * @param $print_name * Shows a friendly print name of the alias instead * of the actual alias itself. This argument is only * checked if the token module is installed. * * @return string * The me alias, token replaced if appropriate. */ function _me_get_me_alias($print = FALSE) { $alias = me_variable_get('me_alias'); // Replace with any global tokens that might have been used in the alias. if (module_exists('token')) { $replaced_alias = token_replace($alias, 'global'); // They will not match if a replacement happened. if ($print && $replaced_alias != $alias) { $alias = ucwords(str_replace(array('-', '[', ']'), array(' ', ''), $alias)); } else { $alias = $replaced_alias; } } return $alias; } /** * A helper function to check if a string is equal to the 'me' alias. * * @param $arg * The argument to check. * * @return boolean * TRUE if the argument given is a 'me' alias. FALSE otherwise. */ function _me_is_alias($arg) { $compare_function = me_variable_get('me_case_insensitive') ? 'strcasecmp' : 'strcmp'; return $compare_function($arg, _me_get_me_alias()) === 0; } /** * Implementation of Views' hook_views_api(). * * See http://drupal.org/project/views for module and hook information. */ function me_views_api() { return array( 'api' => views_api_version(), 'path' => drupal_get_path('module', 'me') .'/includes', ); } /** * Implementation of hook_menu(). */ function me_menu() { $items = array(); $items['admin/settings/me'] = array( 'title' => "'Me' Aliases", 'description' => "Configure the 'me' aliases, and how they're matched.", 'page callback' => 'drupal_get_form', 'page arguments' => array('me_admin_settings_form'), 'access arguments' => array('administer site configuration'), 'type' => MENU_NORMAL_ITEM, ); // TODO: Remove when/if http://drupal.org/node/109588 gets in. // We don't need this here if we are doing redirects. if (!me_variable_get('me_redirect')) { $items['user/'. me_variable_get('me_alias') .'/edit'] = array( 'title' => 'Edit', 'page callback' => 'me_user_edit', 'access callback' => 'me_user_edit_access', 'type' => MENU_LOCAL_TASK, ); } return $items; } /** * Menu callback to redirect to the user edit pages with the correct * user id. * * TODO: Remove when/if http://drupal.org/node/109588 gets in. */ function me_user_edit() { drupal_goto('user/'. $GLOBALS['user']->uid .'/edit'); } /** * Menu access callback to check access before user edit redirection * takes place. This keeps 'me' in the path when access will be denied * anyway, and saves an extra request. * * TODO: Remove when/if http://drupal.org/node/109588 gets in. */ function me_user_edit_access() { return user_edit_access($GLOBALS['user']); } /** * Implementation of hook_perm(). */ function me_perm() { return array( 'use PHP for me alias paths', ); } /** * Form callback for the admin settings form. */ function me_admin_settings_form(&$form_state) { $form = array(); $form['me_alias'] = array( '#type' => 'textfield', '#title' => t("'Me' Alias"), '#description' => t('The alias to use to represent the current users uid.'), '#default_value' => me_variable_get('me_alias'), '#required' => TRUE, ); if (module_exists('token')) { $form['me_token_help'] = array( '#title' => t('Replacement patterns for me alias'), '#type' => 'fieldset', '#collapsible' => TRUE, '#collapsed' => TRUE, 'help' => array( '#type' => 'markup', '#value' => theme('token_help', 'global'), ), ); } $form['me_case_insensitive'] = array( '#type' => 'checkbox', '#title' => t('Case Insensitive Alias Checking'), '#description' => t('When checked, "Me" will be matched the same as "me", "ME", and "mE".'), '#default_value' => me_variable_get('me_case_insensitive'), ); $form['me_rewrite_link'] = array( '#type' => 'checkbox', '#title' => t('Rewrite links generated by the drupal menu system'), '#description' => t('When checked, links output by the drupal menu system will replace uid with the me alias.'), '#default_value' => me_variable_get('me_rewrite_link'), ); $form['me_user_override'] = array( '#type' => 'checkbox', '#title' => t('Allow users to turn off me for their account'), '#default_value' => me_variable_get('me_user_override'), ); $form['me_redirect'] = array( '#type' => 'checkbox', '#title' => t('Redirect to uid'), '#description' => t('When checked, perform a redirect so the users uid is shown in the address bar instead of the me alias.'), '#default_value' => me_variable_get('me_redirect'), ); $form['me_redirect_anonymous'] = array( '#type' => 'textfield', '#title' => t('Redirect anonymous users'), '#description' => t('When this is non-empty, anonymous users will be redirected to the specified drupal path.'), '#default_value' => me_variable_get('me_redirect_anonymous'), ); $access = user_access('use PHP for me alias paths'); $path_rule = me_variable_get('me_path_rule'); $paths = me_variable_get('me_paths'); if ($path_rule == ME_PATH_PHP && !$access) { $form['me_paths_settings'] = array(); $form['me_paths_settings']['me_path_rule'] = array('#type' => 'value', '#value' => $path_rule); $form['me_paths_settings']['me_paths'] = array('#type' => 'value', '#value' => $paths); } else { $options = array( ME_PATH_EXCLUDE => t('Use me alias on every path except the listed paths.'), ME_PATH_INCLUDE => t('Use me alias only on the listed paths.'), ); $description = t("Enter one path per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '')); if ($access) { $options[ME_PATH_PHP] = t('Use me alias if the following PHP code returns TRUE (PHP-mode, experts only).'); $description .= ' '. t('If the PHP-mode is chosen, enter PHP code between %php. Note that executing incorrect PHP-code can break your Drupal site.', array('%php' => '')); } $form['me_paths_settings']['me_path_rule'] = array( '#type' => 'radios', '#title' => t('Use me alias on specific paths'), '#options' => $options, '#default_value' => $path_rule, ); $form['me_paths_settings']['me_paths'] = array( '#type' => 'textarea', '#title' => t('Paths'), '#default_value' => $paths, '#description' => $description . t('

NOTE: This option simply ensures that the browser address bar for these paths have the uid and not me. The me alias will still work for these paths. It will have no effect on specific uids in paths, but if the path includes the me alias, then me will be affected for those paths. This will only affect paths that me can already handle. It will not allow me to work for unknown paths.

'), ); } $form['#validate'] = array('me_admin_settings_form_validate'); $form = system_settings_form($form); // Quite a few options only have an affect on theme and menu rebuilds. We just do them here // to make sure the options have an instant effect. $form['#submit'][] = 'menu_rebuild'; $form['#submit'][] = 'drupal_rebuild_theme_registry'; return $form; } /** * Validation callback for me_admin_settings_form. */ function me_admin_settings_form_validate($form, &$form_state) { // If the token module is installed, we need to also allow a list of tokens // that are allowed to match against. We include all global tokens here, even though // some of them don't really make sense, but that is up to the end user. $token_list = array(); if (module_exists('token')) { $token_list = array_map(create_function('$n', 'return "[$n]";'), array_keys(array_pop(token_get_list('global')))); } if (preg_match('/[^a-zA-Z]/', $form_state['values']['me_alias']) && !in_array($form_state['values']['me_alias'], $token_list)) { if (!empty($token_list)) { $message = t('The alias can only contain characters from a-z and A-Z, or one of the tokens specified in the "Replacement patterns for me alias" section.'); } else { $message = t('The alias can only contain characters from a-z and A-Z.'); } form_set_error('me_alias', $message); } } /** * Implementation of hook_user(). */ function me_user($op, &$edit, &$account, $category = NULL) { switch ($op) { case 'categories': if (me_variable_get('me_user_override')) { return array( array( 'name' => 'me', 'title' => t("'@me' alias", array('@me' => _me_get_me_alias(TRUE))), 'weight' => 2, ), ); } break; case 'form' && $category == 'me': $form = array(); $form['me_uid'] = array( '#type' => 'item', '#title' => t('User id'), '#value' => $account->uid, ); $form['me_disable'] = array( '#type' => 'checkbox', '#title' => t("Disable '%me' alias for this account", array('%me' => _me_get_me_alias(TRUE))), '#description' => t('This option stops your user id from being replaced with %me. %me will still work when entered into the address bar, but you will be redirected to a page with your uid in its place.', array('%me' => _me_get_me_alias(TRUE))), '#default_value' => !empty($account->me_disable), ); return $form; case 'view': if (me_variable_get('me_user_override')) { $enabled = 'enabled'; if (!empty($account->me_disable)) { $enabled = 'disabled'; } $account->content['me'] = array( '#type' => 'markup', '#value' => t("'%me' aliases are $enabled for this account. Account user id is '@uid'.", array('%me' => _me_get_me_alias(TRUE), '@uid' => $account->uid)), '#weight' => 10, ); } break; } }