How to make unpublished nodes "404 Not Found" instead of "Access Denied" for those without access
Drupal 8+ example
Starting with Drupal 8, we can use Symfony's Event subscriber to achieve the desired result
First, we need to define the EventSubscriber in our services file
my_module.services.yml
services:
my_module.exception_listener:
class: Drupal\my_module\EventSubscriber\AccessDeniedAsNotFoundEventSubscriber
arguments: ['@router.admin_context']
tags:
- { name: event_subscriber }
Then, the EventSubscriber itself
/my_module/src/EventSubscriber/AccessDeniedAsNotFoundEventSubscriber.php
<?php
namespace Drupal\my_module\EventSubscriber;
use Drupal\Core\Routing\AdminContext;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* Provides a subscriber to set the properly exception.
*/
class AccessDeniedAsNotFoundEventSubscriber implements EventSubscriberInterface {
/**
* The admin context.
*
* @var \Drupal\Core\Routing\AdminContext
*/
protected $adminContext;
/**
* {@inheritdoc}
*/
public function __construct(AdminContext $admin_context) {
$this->adminContext = $admin_context;
}
/**
* Registers the methods in this class that should be listeners.
*
* @return array
* An array of event listener definitions.
*/
public static function getSubscribedEvents() {
$events[KernelEvents::EXCEPTION][] = ['onAccessDeniedException', 50];
return $events;
}
/**
* Set the properly exception for event.
*
* @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
* The response for exception event.
*/
public function onAccessDeniedException(GetResponseForExceptionEvent $event) {
if ($event->getException() instanceof AccessDeniedHttpException && !$this->adminContext->isAdminRoute()) {
$event->setException(new NotFoundHttpException());
}
}
}
Drupal 7 example
/**
* Implements hook_page_delivery_callback_alter().
*/
function mymodule_page_delivery_callback_alter(&$delivery_callback) {
if ($router_item = menu_get_item()) {
// Check for a node using the normal node_access callback
if (isset($router_item['access_callback']) && $router_item['access_callback'] == 'node_access') {
// Check for what would normally be an access denied
if (empty($router_item['access'])) {
// Check the status is unpublished
if ($node = node_load($router_item['original_map']['1'])) {
if (empty($node->status)) {
$delivery_callback = 'mymodule_deliver_page_not_found';
}
}
}
}
}
}
/**
* Callback for mymodule_page_delivery_callback_alter()
* to show the page not found content.
*/
function mymodule_deliver_page_not_found() {
drupal_deliver_html_page(MENU_NOT_FOUND);
}