Including a page specified in query string
Common practice is to check all GET/POST/SERVER/ENV parameter values against a whitelist or regex before using it for any purpose. You are checking it with a list derived from scandir, but that makes the assumption that the modules directory hasn't been compromised.
Running scandir on every request is a likely performance bottleneck. One possible solution is to cache the results of the scandir and subsequent filtering to a file above the document root and only re-run the scan if the mod time of the modules directory is more recent than the mod time of the cache file (check out the filemtime() docs).
You have some duplicate code for handling 404s. This could be combined into one method if you want to adhere to DRY principles.
A more widely accepted way to prevent a file from being executed is to compare the __FILE__ constant to the SCRIPT_FILENAME which has been set by the server (if available in the $_SERVER global). It's not fool proof, but more portable than your solution.