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_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_variable_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; } /** * 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_variable_get('me_alias')) === 0; } /** * Implementation of hook_views_api(). */ 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, ); $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 (preg_match('/[^a-zA-Z]/', $form_state['values']['me_alias'])) { form_set_error('me_alias', t('The alias can only contain characters from a-z and A-Z.')); } } /** * 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_variable_get('me_alias'))), '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_variable_get('me_alias'))), '#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_variable_get('me_alias'))), '#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_variable_get('me_alias'), '@uid' => $account->uid)), '#weight' => 10, ); } break; } }