Repository: aiogram/aiogram

Issue:
Data Not Being Passed from Parent Router's Filter to Handler in Inherited Router
### Checklist

- [X] I am sure the error is coming from aiogram code
- [X] I have searched in the issue tracker for similar bug reports, including closed ones

### Operating system

Windows 10

### Python version

3.10

### aiogram version

3.0.0rc1

### Expected behavior


When the command "/admin" is sent to the bot, the filter AdminFilter should identify whether the text "admin" is present in the message. If it is, the is_admin variable should be passed to the admin_command_handler, allowing it to execute its logic.



### Current behavior

The variable is_admin is not being passed to the admin_command_handler, resulting in an error indicating that the variable is not defined.

### Steps to reproduce

1. Replace "YOUR_TOKEN_HERE" with your bot token in the code snippet.
2. Run the code.
3. Send the "/admin" command to the bot.
4. Observe the error indicating that the variable is_admin is not defined.

### Code example

```python3
import asyncio
import logging
import sys

from aiogram import Bot, Dispatcher, Router
from aiogram.filters import BaseFilter, Command

dp = Dispatcher()
token = "YOUR_TOKEN_HERE"
bot = Bot(token)


class IsAdmins(BaseFilter):
    async def __call__(self, message):
        if "admin" in message.text.casefold():
            return {"is_admin": True}
        return False



async def admin_command_handler(message, is_admin: bool):
    await message.answer(f"Admin Command Handler, is_admin: {is_admin}")



admin_router = Router()
admin_router.message.filter(IsAdmins())

admin_command_router = Router()

admin_router.include_router(admin_command_router)
dp.include_router(admin_router)

admin_command_router.message.register(adm_com_router, Command("admin"))


async def main():
    await dp.start_polling(bot)


logging.basicConfig(level=logging.INFO, stream=sys.stdout)

asyncio.run(main())
```


### Logs

_No response_

### Additional information

_No response_

Discussion:
github-actions[bot]: # :heavy_check_mark: Changelog found.

Thank you for adding a description of the changes
github-actions[bot]: # :heavy_check_mark: Changelog found.

Thank you for adding a description of the changes
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/aiogram/aiogram/pull/1267?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) Report
> Merging [#1267](https://app.codecov.io/gh/aiogram/aiogram/pull/1267?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (a95a971) into [dev-3.x](https://app.codecov.io/gh/aiogram/aiogram/commit/4d12e073ee49c3aa89c9ad2284e2eb70e0817705?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) (4d12e07) will **not change** coverage.
> Report is 4 commits behind head on dev-3.x.
> The diff coverage is `100.00%`.

[![Impacted file tree graph](https://app.codecov.io/gh/aiogram/aiogram/pull/1267/graphs/tree.svg?width=650&height=150&src=pr&token=cFqEr3p9MX&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram)](https://app.codecov.io/gh/aiogram/aiogram/pull/1267?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram)

```diff
@@            Coverage Diff            @@
##           dev-3.x     #1267   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files          364       364           
  Lines         9313      9313           
=========================================
  Hits          9313      9313           
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `100.00% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Files Changed](https://app.codecov.io/gh/aiogram/aiogram/pull/1267?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram) | Coverage Δ | |
|---|---|---|
| [aiogram/dispatcher/event/telegram.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1267?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9kaXNwYXRjaGVyL2V2ZW50L3RlbGVncmFtLnB5) | `100.00% <100.00%> (ø)` | |
| [aiogram/dispatcher/router.py](https://app.codecov.io/gh/aiogram/aiogram/pull/1267?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=aiogram#diff-YWlvZ3JhbS9kaXNwYXRjaGVyL3JvdXRlci5weQ==) | `100.00% <100.00%> (ø)` | |


Patch:
diff --git a/CHANGES/1266.bugfix.rst b/CHANGES/1266.bugfix.rst
new file mode 100644
index 00000000..55f0cfc6
--- /dev/null
+++ b/CHANGES/1266.bugfix.rst
@@ -0,0 +1,2 @@
+Moved global filters check placement into router to add chance to pass context from global filters
+into handlers in the same way as it possible in other places
diff --git a/aiogram/dispatcher/event/telegram.py b/aiogram/dispatcher/event/telegram.py
index a468816c..cb4039c7 100644
--- a/aiogram/dispatcher/event/telegram.py
+++ b/aiogram/dispatcher/event/telegram.py
@@ -101,17 +101,14 @@ class TelegramEventObserver:
         )
         return wrapped_outer(event, data)
 
+    def check_root_filters(self, event: TelegramObject, **kwargs: Any) -> Any:
+        return self._handler.check(event, **kwargs)
+
     async def trigger(self, event: TelegramObject, **kwargs: Any) -> Any:
         """
         Propagate event to handlers and stops propagation on first match.
-        Handler will be called when all its filters is pass.
+        Handler will be called when all its filters are pass.
         """
-        # Check globally defined filters before any other handler will be checked
-        result, data = await self._handler.check(event, **kwargs)
-        if not result:
-            return REJECTED
-        kwargs.update(data)
-
         for handler in self.handlers:
             result, data = await handler.check(event, **kwargs)
             if result:
diff --git a/aiogram/dispatcher/router.py b/aiogram/dispatcher/router.py
index 423b7173..4dd4284f 100644
--- a/aiogram/dispatcher/router.py
+++ b/aiogram/dispatcher/router.py
@@ -125,8 +125,17 @@ class Router:
     ) -> Any:
         response = UNHANDLED
         if observer:
+            # Check globally defined filters before any other handler will be checked.
+            # This check is placed here instead of `trigger` method to add possibility
+            # to pass context to handlers from global filters.
+            result, data = await observer.check_root_filters(event, **kwargs)
+            if not result:
+                return UNHANDLED
+            kwargs.update(data)
+
             response = await observer.trigger(event, **kwargs)
-            if response is REJECTED:
+            if response is REJECTED:  # pragma: no cover
+                # Possible only if some handler returns REJECTED
                 return UNHANDLED
             if response is not UNHANDLED:
                 return response
diff --git a/examples/context_addition_from_filter.py b/examples/context_addition_from_filter.py
index e4eca517..40338940 100644
--- a/examples/context_addition_from_filter.py
+++ b/examples/context_addition_from_filter.py
@@ -4,7 +4,6 @@ from aiogram import Router
 from aiogram.filters import Filter
 from aiogram.types import Message, User
 
-
 router = Router(name=__name__)
 
 
@@ -28,6 +27,4 @@ class HelloFilter(Filter):
 async def my_handler(
     message: Message, name: str  # Now we can accept "name" as named parameter
 ) -> Any:
-    return message.answer(
-        "Hello, {name}!".format(name=name)
-    )
+    return message.answer("Hello, {name}!".format(name=name))
diff --git a/examples/multibot.py b/examples/multibot.py
index 82fac4a1..d5dbe5fc 100644
--- a/examples/multibot.py
+++ b/examples/multibot.py
@@ -4,6 +4,7 @@ from os import getenv
 from typing import Any, Dict, Union
 
 from aiohttp import web
+from finite_state_machine import form_router
 
 from aiogram import Bot, Dispatcher, F, Router
 from aiogram.client.session.aiohttp import AiohttpSession
@@ -18,7 +19,6 @@ from aiogram.webhook.aiohttp_server import (
     TokenBasedRequestHandler,
     setup_application,
 )
-from finite_state_machine import form_router
 
 main_router = Router()
 


Test patch:
diff --git a/tests/test_dispatcher/test_event/test_telegram.py b/tests/test_dispatcher/test_event/test_telegram.py
index 973fa17c..8976b2ff 100644
--- a/tests/test_dispatcher/test_event/test_telegram.py
+++ b/tests/test_dispatcher/test_event/test_telegram.py
@@ -5,7 +5,7 @@ from typing import Any, Dict, NoReturn, Optional, Union
 import pytest
 from pydantic import BaseModel
 
-from aiogram.dispatcher.event.bases import REJECTED, SkipHandler
+from aiogram.dispatcher.event.bases import SkipHandler, UNHANDLED
 from aiogram.dispatcher.event.handler import HandlerObject
 from aiogram.dispatcher.event.telegram import TelegramEventObserver
 from aiogram.dispatcher.router import Router
@@ -13,6 +13,7 @@ from aiogram.exceptions import UnsupportedKeywordArgument
 from aiogram.filters import Filter
 from aiogram.types import Chat, Message, User
 
+
 # TODO: Test middlewares in routers tree
 
 
@@ -220,8 +221,8 @@ class TestTelegramEventObserver:
         r1.message.register(handler)
         r2.message.register(handler)
 
-        assert await r1.message.trigger(None) is REJECTED
-        assert await r2.message.trigger(None) is None
+        assert await r1.propagate_event("message", None) is UNHANDLED
+        assert await r2.propagate_event("message", None) is None
 
     async def test_global_filter_in_nested_router(self):
         r1 = Router()
@@ -234,4 +235,4 @@ class TestTelegramEventObserver:
         r1.message.filter(lambda evt: False)
         r2.message.register(handler)
 
-        assert await r1.message.trigger(None) is REJECTED
+        assert await r1.message.trigger(None) is UNHANDLED