Issue #2106709 by tim.plunkett: Remove legacy router backwards compatibility layer.
parent
dc436055f7
commit
652c334234
|
@ -255,19 +255,11 @@ services:
|
|||
router.dynamic:
|
||||
class: Symfony\Cmf\Component\Routing\DynamicRouter
|
||||
arguments: ['@router.request_context', '@router.matcher', '@url_generator']
|
||||
legacy_generator:
|
||||
class: Drupal\Core\Routing\NullGenerator
|
||||
legacy_url_matcher:
|
||||
class: Drupal\Core\LegacyUrlMatcher
|
||||
legacy_router:
|
||||
class: Symfony\Cmf\Component\Routing\DynamicRouter
|
||||
arguments: ['@router.request_context', '@legacy_url_matcher', '@legacy_generator']
|
||||
router:
|
||||
class: Symfony\Cmf\Component\Routing\ChainRouter
|
||||
calls:
|
||||
- [setContext, ['@router.request_context']]
|
||||
- [add, ['@router.dynamic']]
|
||||
- [add, ['@legacy_router']]
|
||||
entity.query:
|
||||
class: Drupal\Core\Entity\Query\QueryFactory
|
||||
arguments: ['@entity.manager']
|
||||
|
@ -383,10 +375,6 @@ services:
|
|||
tags:
|
||||
- { name: event_subscriber }
|
||||
arguments: ['@content_negotiation']
|
||||
legacy_access_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\LegacyAccessSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
private_key:
|
||||
class: Drupal\Core\PrivateKey
|
||||
arguments: ['@state']
|
||||
|
@ -436,10 +424,6 @@ services:
|
|||
class: Drupal\Core\EventSubscriber\LegacyRequestSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
legacy_controller_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\LegacyControllerSubscriber
|
||||
tags:
|
||||
- { name: event_subscriber }
|
||||
finish_response_subscriber:
|
||||
class: Drupal\Core\EventSubscriber\FinishResponseSubscriber
|
||||
tags:
|
||||
|
|
|
@ -204,9 +204,9 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
|||
* $commands[] = ajax_command_replace('#object-1', 'some html here');
|
||||
* // Add a visual "changed" marker to the '#object-1' element.
|
||||
* $commands[] = ajax_command_changed('#object-1');
|
||||
* // Menu 'page callback' and #ajax['callback'] functions are supposed to
|
||||
* // return render arrays. If returning an Ajax commands array, it must be
|
||||
* // encapsulated in a render array structure.
|
||||
* // #ajax['callback'] functions are supposed to return render arrays. If
|
||||
* // returning an Ajax commands array, it must be encapsulated in a render
|
||||
* // array structure.
|
||||
* return array('#type' => 'ajax', '#commands' => $commands);
|
||||
* @endcode
|
||||
*
|
||||
|
|
|
@ -976,7 +976,7 @@ function menu_item_route_access(Route $route, $href, &$map) {
|
|||
// Attempt to match this path to provide a fully built request to the
|
||||
// access checker.
|
||||
try {
|
||||
$request->attributes->add(\Drupal::service('router.dynamic')->matchRequest($request));
|
||||
$request->attributes->add(\Drupal::service('router')->matchRequest($request));
|
||||
}
|
||||
catch (NotFoundHttpException $e) {
|
||||
return FALSE;
|
||||
|
|
|
@ -178,17 +178,8 @@ class AuthenticationManager implements AuthenticationProviderInterface, Authenti
|
|||
public function handleException(GetResponseForExceptionEvent $event) {
|
||||
$request = $event->getRequest();
|
||||
|
||||
// Legacy routes won't have a Route object; they have drupal_menu_item
|
||||
// instead. Assume those were authenticated by cookie, because the legacy
|
||||
// router didn't support anything else.
|
||||
// @todo Remove this check once the old router is fully removed.
|
||||
if ($request->attributes->has('_drupal_menu_item')) {
|
||||
$active_providers = array('cookie');
|
||||
}
|
||||
else {
|
||||
$route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
|
||||
$active_providers = ($route && $route->getOption('_auth')) ? $route->getOption('_auth') : array($this->defaultProviderId());
|
||||
}
|
||||
$route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT);
|
||||
$active_providers = ($route && $route->getOption('_auth')) ? $route->getOption('_auth') : array($this->defaultProviderId());
|
||||
|
||||
// Get the sorted list of active providers for the given route.
|
||||
$providers = array_intersect($active_providers, array_keys($this->providers));
|
||||
|
|
|
@ -32,11 +32,5 @@ class RegisterRouteEnhancersPass implements CompilerPassInterface {
|
|||
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
||||
$router->addMethodCall('addRouteEnhancer', array(new Reference($id), $priority));
|
||||
}
|
||||
|
||||
$legacy_router = $container->getDefinition('legacy_router');
|
||||
foreach ($container->findTaggedServiceIds('legacy_route_enhancer') as $id => $attributes) {
|
||||
$priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
|
||||
$legacy_router->addMethodCall('addRouteEnhancer', array(new Reference($id), $priority));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\Core\EventSubscriber\LegacyAccessSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Access subscriber for legacy controller requests.
|
||||
*/
|
||||
class LegacyAccessSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Verifies that the current user can access the requested path.
|
||||
*
|
||||
* @todo This is a total hack to keep our current access system working. It
|
||||
* should be replaced with something robust and injected at some point.
|
||||
*
|
||||
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
|
||||
* The Event to process.
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException
|
||||
*/
|
||||
public function onKernelRequestAccessCheck(GetResponseEvent $event) {
|
||||
|
||||
$request_attributes = $event->getRequest()->attributes;
|
||||
|
||||
$router_item = $request_attributes->get('_drupal_menu_item');
|
||||
|
||||
// For legacy routes we do not allow any user not authenticated by cookie
|
||||
// provider.
|
||||
$provider = $request_attributes->get('_authentication_provider');
|
||||
if ($request_attributes->get('_legacy') && $provider && $provider != 'cookie') {
|
||||
$GLOBALS['user'] = drupal_anonymous_user();
|
||||
$request_attributes->set('_account', $GLOBALS['user']);
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
|
||||
if (isset($router_item['access']) && !$router_item['access']) {
|
||||
throw new AccessDeniedHttpException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the methods in this class that should be listeners.
|
||||
*
|
||||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[KernelEvents::REQUEST][] = array('onKernelRequestAccessCheck', 30);
|
||||
|
||||
return $events;
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\EventSubscriber\LegacyControllerSubscriber.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core\EventSubscriber;
|
||||
|
||||
use Symfony\Component\HttpKernel\KernelEvents;
|
||||
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
/**
|
||||
* Access subscriber for controller requests.
|
||||
*/
|
||||
class LegacyControllerSubscriber implements EventSubscriberInterface {
|
||||
|
||||
/**
|
||||
* Wraps legacy controllers in a closure to handle old-style arguments.
|
||||
*
|
||||
* This is a backward compatibility layer only. This is a rather ugly way
|
||||
* to piggyback Drupal's existing menu router items onto the Symfony model,
|
||||
* but it works for now. If we did not do this, any menu router item with
|
||||
* a variable number of arguments would fail to work. This bypasses Symfony's
|
||||
* controller argument handling entirely and lets the old-style approach work.
|
||||
*
|
||||
* @todo Convert Drupal to use the IETF-draft-RFC style {placeholders}. That
|
||||
* will allow us to use the native Symfony conversion, including
|
||||
* out-of-order argument mapping, name-based mapping, and with another
|
||||
* listener auto-conversion of parameters to full objects. That may
|
||||
* necessitate not using func_get_args()-based controllers. That is likely
|
||||
* for the best, as those are quite hard to document anyway.
|
||||
*
|
||||
* @param Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
|
||||
* The Event to process.
|
||||
*/
|
||||
public function onKernelControllerLegacy(FilterControllerEvent $event) {
|
||||
$request = $event->getRequest();
|
||||
|
||||
// If we're dealing with a legacy route, wrap the controller in a closure
|
||||
// so parameters still work.
|
||||
if ($request->attributes->get('_legacy')) {
|
||||
$router_item = $request->attributes->get('_drupal_menu_item');
|
||||
$new_controller = function() use ($router_item) {
|
||||
return call_user_func_array($router_item['page_callback'], $router_item['page_arguments']);
|
||||
};
|
||||
$event->setController($new_controller);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the methods in this class that should be listeners.
|
||||
*
|
||||
* @return array
|
||||
* An array of event listener definitions.
|
||||
*/
|
||||
static function getSubscribedEvents() {
|
||||
$events[KernelEvents::CONTROLLER][] = array('onKernelControllerLegacy', 30);
|
||||
|
||||
return $events;
|
||||
}
|
||||
}
|
|
@ -1,177 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\Core\LegacyUrlMatcher.
|
||||
*/
|
||||
|
||||
namespace Drupal\Core;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
|
||||
use Symfony\Component\Routing\Matcher\RequestMatcherInterface;
|
||||
use Symfony\Component\Routing\RequestContextAwareInterface;
|
||||
use Symfony\Component\Routing\RequestContext;
|
||||
|
||||
/**
|
||||
* UrlMatcher matches URL based on a set of routes.
|
||||
*/
|
||||
class LegacyUrlMatcher implements RequestMatcherInterface, RequestContextAwareInterface {
|
||||
|
||||
/**
|
||||
* The request context for this matcher.
|
||||
*
|
||||
* @var Symfony\Component\Routing\RequestContext
|
||||
*/
|
||||
protected $context;
|
||||
|
||||
/**
|
||||
* The request object for this matcher.
|
||||
*
|
||||
* @var Symfony\Component\HttpFoundation\Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
// We will not actually use this object, but it's needed to conform to
|
||||
// the interface.
|
||||
$this->context = new RequestContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request context.
|
||||
*
|
||||
* This method is just to satisfy the interface, and is largely vestigial.
|
||||
* The request context object does not contain the information we need, so
|
||||
* we will use the original request object.
|
||||
*
|
||||
* @param Symfony\Component\Routing\RequestContext $context
|
||||
* The context.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function setContext(RequestContext $context) {
|
||||
$this->context = $context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request context.
|
||||
*
|
||||
* This method is just to satisfy the interface, and is largely vestigial.
|
||||
* The request context object does not contain the information we need, so
|
||||
* we will use the original request object.
|
||||
*
|
||||
* @return Symfony\Component\Routing\RequestContext
|
||||
* The context.
|
||||
*/
|
||||
public function getContext() {
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the request object to use.
|
||||
*
|
||||
* This is used by the RouterListener to make additional request attributes
|
||||
* available.
|
||||
*
|
||||
* @param Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
*/
|
||||
public function setRequest(Request $request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request object.
|
||||
*
|
||||
* @return Symfony\Component\HttpFoundation\Request $request
|
||||
* The request object.
|
||||
*/
|
||||
public function getRequest() {
|
||||
return $this->request;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
public function matchRequest(Request $request) {
|
||||
if ($router_item = $this->matchDrupalItem($request->attributes->get('_system_path'))) {
|
||||
$ret = $this->convertDrupalItem($router_item);
|
||||
// Stash the router item in the attributes while we're transitioning.
|
||||
$ret['_drupal_menu_item'] = $router_item;
|
||||
|
||||
// Most legacy controllers (aka page callbacks) are in a separate file,
|
||||
// so we have to include that.
|
||||
if ($router_item['include_file']) {
|
||||
require_once DRUPAL_ROOT . '/' . $router_item['include_file'];
|
||||
}
|
||||
|
||||
// Flag this as a legacy request. We need to use this for subrequest
|
||||
// handling so that we can treat older page callbacks and new routes
|
||||
// differently.
|
||||
// @todo Remove this line as soon as possible.
|
||||
$ret['_legacy'] = TRUE;
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
// This matcher doesn't differentiate by method, so don't bother with those
|
||||
// exceptions.
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Drupal menu item.
|
||||
*
|
||||
* @todo Make this return multiple possible candidates for the resolver to
|
||||
* consider.
|
||||
*
|
||||
* @param string $path
|
||||
* The path being looked up by
|
||||
*/
|
||||
protected function matchDrupalItem($path) {
|
||||
// For now we can just proxy our procedural method. At some point this will
|
||||
// become more complicated because we'll need to get back candidates for a
|
||||
// path and them resolve them based on things like method and scheme which
|
||||
// we currently can't do.
|
||||
return menu_get_item($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a Drupal menu item to a route array.
|
||||
*
|
||||
* @param array $router_item
|
||||
* The Drupal menu item.
|
||||
*
|
||||
* @return
|
||||
* An array of parameters.
|
||||
*/
|
||||
protected function convertDrupalItem($router_item) {
|
||||
$route = array(
|
||||
'_controller' => $router_item['page_callback']
|
||||
);
|
||||
|
||||
// A few menu items have a fake page callback temporarily. Skip those,
|
||||
// we aren't going to route them.
|
||||
if ($router_item['page_callback'] == 'USES_ROUTE') {
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
|
||||
// @todo menu_get_item() does not unserialize page arguments when the access
|
||||
// is denied. Remove this temporary hack that always does that.
|
||||
if (!is_array($router_item['page_arguments'])) {
|
||||
$router_item['page_arguments'] = unserialize($router_item['page_arguments']);
|
||||
}
|
||||
|
||||
// Place argument defaults on the route.
|
||||
foreach ($router_item['page_arguments'] as $k => $v) {
|
||||
$route[$k] = $v;
|
||||
}
|
||||
return $route;
|
||||
}
|
||||
}
|
|
@ -583,10 +583,7 @@ class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
|
|||
$request = Request::create('/' . $link_path);
|
||||
$request->attributes->set('_system_path', $link_path);
|
||||
try {
|
||||
// Use router.dynamic instead of router, because router will call the
|
||||
// legacy router which will call hook_menu() and you will get back to
|
||||
// this method.
|
||||
$result = \Drupal::service('router.dynamic')->matchRequest($request);
|
||||
$result = \Drupal::service('router')->matchRequest($request);
|
||||
$return = array();
|
||||
$return[] = isset($result['_route']) ? $result['_route'] : '';
|
||||
$return[] = $result['_raw_variables']->all();
|
||||
|
|
|
@ -40,7 +40,7 @@ class MenuLinkTest extends UnitTestCase {
|
|||
public function testFindRouteNameParameters() {
|
||||
$router = $this->getMock('Symfony\Component\Routing\Matcher\RequestMatcherInterface');
|
||||
$container = new ContainerBuilder();
|
||||
$container->set('router.dynamic', $router);
|
||||
$container->set('router', $router);
|
||||
\Drupal::setContainer($container);
|
||||
|
||||
$router->expects($this->at(0))
|
||||
|
|
|
@ -136,16 +136,6 @@ class PathBasedBreadcrumbBuilder extends BreadcrumbBuilderBase {
|
|||
$title = $this->titleResolver->getTitle($route_request, $route_request->attributes->get(RouteObjectInterface::ROUTE_OBJECT));
|
||||
}
|
||||
}
|
||||
// @todo - remove this once all of core is converted to the new router.
|
||||
else {
|
||||
$menu_item = $route_request->attributes->get('_drupal_menu_item');
|
||||
// Skip the breadcrumb step for menu items linking to the parent item.
|
||||
if (($menu_item['type'] & MENU_LINKS_TO_PARENT) == MENU_LINKS_TO_PARENT) {
|
||||
continue;
|
||||
}
|
||||
$access = $menu_item['access'];
|
||||
$title = $menu_item['title'];
|
||||
}
|
||||
if ($access) {
|
||||
if (!$title) {
|
||||
// Fallback to using the raw path component as the title if the
|
||||
|
|
|
@ -504,145 +504,6 @@ function hook_menu_get_item_alter(&$router_item, $path, $original_map) {
|
|||
* paths and whose values are an associative array of properties for each
|
||||
* path. (The complete list of properties is in the return value section below.)
|
||||
*
|
||||
* @section sec_callback_funcs Callback Functions
|
||||
* The definition for each path may include a page callback function, which is
|
||||
* invoked when the registered path is requested. If there is no other
|
||||
* registered path that fits the requested path better, any further path
|
||||
* components are passed to the callback function. For example, your module
|
||||
* could register path 'abc/def':
|
||||
* @code
|
||||
* function mymodule_menu() {
|
||||
* $items['abc/def'] = array(
|
||||
* 'page callback' => 'mymodule_abc_view',
|
||||
* );
|
||||
* return $items;
|
||||
* }
|
||||
*
|
||||
* function mymodule_abc_view($ghi = 0, $jkl = '') {
|
||||
* // ...
|
||||
* }
|
||||
* @endcode
|
||||
* When path 'abc/def' is requested, no further path components are in the
|
||||
* request, and no additional arguments are passed to the callback function (so
|
||||
* $ghi and $jkl would take the default values as defined in the function
|
||||
* signature). When 'abc/def/123/foo' is requested, $ghi will be '123' and
|
||||
* $jkl will be 'foo'. Note that this automatic passing of optional path
|
||||
* arguments applies only to page and theme callback functions.
|
||||
*
|
||||
* @subsection sub_callback_arguments Callback Arguments
|
||||
* In addition to optional path arguments, the page callback and other callback
|
||||
* functions may specify argument lists as arrays. These argument lists may
|
||||
* contain both fixed/hard-coded argument values and integers that correspond
|
||||
* to path components. When integers are used and the callback function is
|
||||
* called, the corresponding path components will be substituted for the
|
||||
* integers. That is, the integer 0 in an argument list will be replaced with
|
||||
* the first path component, integer 1 with the second, and so on (path
|
||||
* components are numbered starting from zero). To pass an integer without it
|
||||
* being replaced with its respective path component, use the string value of
|
||||
* the integer (e.g., '1') as the argument value. This substitution feature
|
||||
* allows you to re-use a callback function for several different paths. For
|
||||
* example:
|
||||
* @code
|
||||
* function mymodule_menu() {
|
||||
* $items['abc/def'] = array(
|
||||
* 'page callback' => 'mymodule_abc_view',
|
||||
* 'page arguments' => array(1, 'foo'),
|
||||
* );
|
||||
* return $items;
|
||||
* }
|
||||
* @endcode
|
||||
* When path 'abc/def' is requested, the page callback function will get 'def'
|
||||
* as the first argument and (always) 'foo' as the second argument.
|
||||
*
|
||||
* If a page callback function uses an argument list array, and its path is
|
||||
* requested with optional path arguments, then the list array's arguments are
|
||||
* passed to the callback function first, followed by the optional path
|
||||
* arguments. Using the above example, when path 'abc/def/bar/baz' is requested,
|
||||
* mymodule_abc_view() will be called with 'def', 'foo', 'bar' and 'baz' as
|
||||
* arguments, in that order.
|
||||
*
|
||||
* Special care should be taken for the page callback drupal_get_form(), because
|
||||
* your specific form callback function will always receive $form and
|
||||
* &$form_state as the first function arguments:
|
||||
* @code
|
||||
* function mymodule_abc_form($form, &$form_state) {
|
||||
* // ...
|
||||
* return $form;
|
||||
* }
|
||||
* @endcode
|
||||
* See @link form_api Form API documentation @endlink for details.
|
||||
*
|
||||
* @section sec_path_wildcards Wildcards in Paths
|
||||
* @subsection sub_simple_wildcards Simple Wildcards
|
||||
* Wildcards within paths also work with integer substitution. For example,
|
||||
* your module could register path 'my-module/%/edit':
|
||||
* @code
|
||||
* $items['my-module/%/edit'] = array(
|
||||
* 'page callback' => 'mymodule_abc_edit',
|
||||
* 'page arguments' => array(1),
|
||||
* );
|
||||
* @endcode
|
||||
* When path 'my-module/foo/edit' is requested, integer 1 will be replaced
|
||||
* with 'foo' and passed to the callback function. Note that wildcards may not
|
||||
* be used as the first component.
|
||||
*
|
||||
* @subsection sub_autoload_wildcards Auto-Loader Wildcards
|
||||
* Registered paths may also contain special "auto-loader" wildcard components
|
||||
* in the form of '%mymodule_abc', where the '%' part means that this path
|
||||
* component is a wildcard, and the 'mymodule_abc' part defines the prefix for a
|
||||
* load function, which here would be named mymodule_abc_load(). When a matching
|
||||
* path is requested, your load function will receive as its first argument the
|
||||
* path component in the position of the wildcard; load functions may also be
|
||||
* passed additional arguments (see "load arguments" in the return value
|
||||
* section below). For example, your module could register path
|
||||
* 'my-module/%mymodule_abc/edit':
|
||||
* @code
|
||||
* $items['my-module/%mymodule_abc/edit'] = array(
|
||||
* 'page callback' => 'mymodule_abc_edit',
|
||||
* 'page arguments' => array(1),
|
||||
* );
|
||||
* @endcode
|
||||
* When path 'my-module/123/edit' is requested, your load function
|
||||
* mymodule_abc_load() will be invoked with the argument '123', and should
|
||||
* load and return an "abc" object with internal id 123:
|
||||
* @code
|
||||
* function mymodule_abc_load($abc_id) {
|
||||
* return db_query("SELECT * FROM {mymodule_abc} WHERE abc_id = :abc_id", array(':abc_id' => $abc_id))->fetchObject();
|
||||
* }
|
||||
* @endcode
|
||||
* This 'abc' object will then be passed into the callback functions defined
|
||||
* for the menu item, such as the page callback function mymodule_abc_edit()
|
||||
* to replace the integer 1 in the argument array. Note that a load function
|
||||
* should return NULL when it is unable to provide a loadable object. For
|
||||
* example, the node_load() function for the 'node/%node/edit' menu item will
|
||||
* return NULL for the path 'node/999/edit' if a node with a node ID of 999
|
||||
* does not exist. The menu routing system will return a 404 error in this case.
|
||||
*
|
||||
* @subsection sub_argument_wildcards Argument Wildcards
|
||||
* You can also define a %wildcard_to_arg() function (for the example menu
|
||||
* entry above this would be 'mymodule_abc_to_arg()'). The _to_arg() function
|
||||
* is invoked to retrieve a value that is used in the path in place of the
|
||||
* wildcard. A good example is user.module, which defines
|
||||
* user_uid_optional_to_arg() (corresponding to the menu entry
|
||||
* 'tracker/%user_uid_optional'). This function returns the user ID of the
|
||||
* current user.
|
||||
*
|
||||
* The _to_arg() function will get called with three arguments:
|
||||
* - $arg: A string representing whatever argument may have been supplied by
|
||||
* the caller (this is particularly useful if you want the _to_arg()
|
||||
* function only supply a (default) value if no other value is specified,
|
||||
* as in the case of user_uid_optional_to_arg().
|
||||
* - $map: An array of all path fragments (e.g. array('node','123','edit') for
|
||||
* 'node/123/edit').
|
||||
* - $index: An integer indicating which element of $map corresponds to $arg.
|
||||
*
|
||||
* _load() and _to_arg() functions may seem similar at first glance, but they
|
||||
* have different purposes and are called at different times. _load()
|
||||
* functions are called when the menu system is collecting arguments to pass
|
||||
* to the callback functions defined for the menu item. _to_arg() functions
|
||||
* are called when the menu system is generating links to related paths, such
|
||||
* as the tabs for a set of MENU_LOCAL_TASK items.
|
||||
*
|
||||
* @section sec_render_tabs Rendering Menu Items As Tabs
|
||||
* You can also make groups of menu items to be rendered (by default) as tabs
|
||||
* on a page. To do that, first create one menu item of type MENU_NORMAL_ITEM,
|
||||
|
|
Loading…
Reference in New Issue