Server-level middleware (outside app context/container)

After much frustration, I realized that middleware only executes within the context/container of the app to which it is registered (makes sense in retrospect). Is it possible to register a middleware that runs at the server-level? Using the “censor” example from the documentation:
https://docs.nextcloud.com/server/latest/developer_manual//basics/middlewares.html?highlight=middleware

Say I want to censor words and phrases globally across the entire system. I would need a middleware that can execute the beforeOutput method for every page, app, etc. Is this possible?

IIRC you have to register that on the server container.

I think I tried that, and it didn’t work as expected. I wound up doing something rather dirty - registering it on all of the other containers. To make sure that my code runs, I named my app “zzzzzzzz” because the containers are instantiated in alphabetical order. As long as that holds, this should work, but I really need to come up with a better solution eventually.

How would you do it on the server container, though? I tried the following:

$this->getContainer()->getServer()->registerService('MyMiddleware', function($c){
	return new MyMiddleware();
});

//This doesn't work
$this->getContainer()->getServer()->registerMiddleware('MyMiddleware');

However, registerMiddleware() isn’t a function of the OC\Server class, so I’m not sure what to do.

Try querying the \OC\AppFramework\Middleware\MiddlewareDispatcher and use \OC\AppFramework\Middleware\MiddlewareDispatcher::registerMiddleware.

I’m still missing something. When I run this code, it throws an error:

Argument 1 passed to OC\AppFramework\Middleware\MiddlewareDispatcher::registerMiddleware() must be an instance of OCP\AppFramework\Middleware, string given

Here’s my code with just the relevant parts:

<?php

namespace OCA\Zzzzzzzzzzzzz\AppInfo;

use OCP\AppFramework\App;
use OCA\Zzzzzzzzzzzzz\Middleware\DomManipulationMiddleware;

class Application extends App {

	public const APP_ID = 'zzzzzzzzzzzzz';

	public function __construct() {
		parent::__construct('zzzzzzzzzzzzz');

		/* This registers the middleware to all applications (my brute-force attempt) */
		foreach (OC_App::getEnabledApps() as $appId) {
			if ($appId != 'zzzzzzzzzzzzz') {
				$appContainer = $this->getContainer()->getServer()->getRegisteredAppContainer($appId);

				$appContainer->registerService('DomManipulationMiddleware', function($c){
					return new DomManipulationMiddleware();
				});
				$appContainer->registerMiddleware('DomManipulationMiddleware');
			}
		}
		/**/

		/* Registers the middleware for this app (I could do this in the previous loop, but
		 * I separated it out so I could try your method) */
		$container = $this->getContainer();

		$container->registerService('DomManipulationMiddleware', function($c){
			return new DomManipulationMiddleware();
		});

		$container->registerMiddleware('DomManipulationMiddleware');

		$this->getContainer()->query(\OC\AppFramework\Middleware\MiddlewareDispatcher::class)->registerMiddleware(new DomManipulationMiddleware());
		$this->getContainer()->getServer()->query(\OC\AppFramework\Middleware\MiddlewareDispatcher::class)->registerMiddleware(new DomManipulationMiddleware());
	}
}

(Recall that I named my app Zzzzzzzzzzzzz because I’ve been registering my middleware against all apps, and apps are instantiated alphabetically. This method successfully lets me register the middleware with installed apps, but I can’t use it to manipulate the login page, for example).

As the error says. Use an instance of the middleware instead of the class string.

Seriously, thank you for your help.

I feel dumb. I was running the command interactively with XDebug instead of actually adding it to the code, and passing in new DomManipulationMiddleware was throwing an error. It works when it’s actually in the script.

However, it still isn’t running my middleware. When
\OC\AppFramework\Middleware\MiddlewareDispatcher::beforeOutput
runs, my middleware isn’t in the list of middlewares.

My code is basically just this, now:

<?php

namespace OCA\Zzzzzzzzzzzzz\AppInfo;

use OCP\AppFramework\App;
use OCA\Zzzzzzzzzzzzz\Middleware\DomManipulationMiddleware;

class Application extends App {

	public const APP_ID = 'zzzzzzzzzzzzz';

	public function __construct() {
		parent::__construct('zzzzzzzzzzzzz');

		// I'm passing both just because I don't know which one will actually work
		$this->getContainer()->query(\OC\AppFramework\Middleware\MiddlewareDispatcher::class)->registerMiddleware(new DomManipulationMiddleware());
		$this->getContainer()->getServer()->query(\OC\AppFramework\Middleware\MiddlewareDispatcher::class)->registerMiddleware(new DomManipulationMiddleware());

		// The middleware is listed here, but not when \OC\AppFramework\Middleware\MiddlewareDispatcher::beforeOutput runs
		$this->getContainer()->getServer()->query(\OC\AppFramework\Middleware\MiddlewareDispatcher::class)->getMiddlewares()
		$this->getContainer()->query(\OC\AppFramework\Middleware\MiddlewareDispatcher::class)->getMiddlewares()
	}
}