├── Controller ├── Config │ └── Save.php └── Router.php ├── Model ├── Ignition.php ├── IgnitionFactory.php ├── IgnitionNonceProvider.php └── Report.php ├── Plugin ├── App.php └── CspNonceProvider.php ├── README.md ├── Solutions ├── RunSetupUpdgradeSolution.php └── SolutionProviders │ ├── ConfigChangedSolutionProvider.php │ ├── ConstructorArgumentExceptionSolutionProvider.php │ ├── DbColumnNotFoundSolutionProvider.php │ ├── DbTableNotFoundSolutionProvider.php │ ├── UpgradeYourDatabaseSolutionProvider.php │ └── XmlValidationErrorSolutionProvider.php ├── composer.json ├── etc ├── di.xml ├── frontend │ ├── di.xml │ └── routes.xml └── module.xml ├── media ├── dark.webp └── light.webp ├── registration.php └── view ├── base ├── requirejs-config.js └── web │ └── js │ └── ignition.js └── frontend └── layout └── breeze_default.xml /Controller/Config/Save.php: -------------------------------------------------------------------------------- 1 | resultFactory->create(); 24 | 25 | if (!$this->request->isPost()) { 26 | return $response->setHttpResponseCode(405)->setData(['error' => 'Method is not allowed']); 27 | } 28 | 29 | try { 30 | $data = $this->serializer->unserialize($this->request->getContent()); 31 | $data = array_intersect_key($data, array_flip([ 32 | 'editor', 33 | 'theme', 34 | 'hide_solutions', 35 | ])); 36 | 37 | $config = new IgnitionConfig(); 38 | if (!$config->saveValues(array_merge($config->getConfigOptions(), $data))) { 39 | throw new Exception('Unable to save Ignition config'); 40 | } 41 | 42 | $response->setData(true); 43 | } catch (Exception $e) { 44 | $response->setHttpResponseCode(400)->setData(['error' => $e->getMessage()]); 45 | } 46 | 47 | return $response; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Controller/Router.php: -------------------------------------------------------------------------------- 1 | actionFactory = $actionFactory; 18 | } 19 | 20 | public function match(RequestInterface $request) 21 | { 22 | $identifier = trim($request->getPathInfo(), '/'); 23 | 24 | if ($identifier !== '_ignition/update-config') { 25 | return false; 26 | } 27 | 28 | $request->setRouteName('_ignition') 29 | ->setControllerName('config') 30 | ->setActionName('save') 31 | ->setAlias(Url::REWRITE_REQUEST_PATH_ALIAS, $identifier); 32 | 33 | return $this->actionFactory->create(Forward::class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Model/Ignition.php: -------------------------------------------------------------------------------- 1 | config->getValue('swissup_ignition/general/api_key', ScopeInterface::SCOPE_WEBSITE); 22 | 23 | return (new Ignition()) 24 | ->applicationPath(BP) 25 | ->sendToFlare($flareApiKey) 26 | ->addSolutionProviders($this->solutionProviders) 27 | ->runningInProductionEnvironment($this->state->getMode() === State::MODE_PRODUCTION); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Model/IgnitionNonceProvider.php: -------------------------------------------------------------------------------- 1 | session->setData('swissup_ignition_nonce', $nonce); 21 | } 22 | 23 | public function generateNonce(): string 24 | { 25 | $nonce = ''; 26 | 27 | if ($this->request->isAjax()) { 28 | // Use previously generated nonce because we will show 29 | // popup on the page generated by previous request 30 | $nonce = (string) $this->session->getData('swissup_ignition_nonce'); 31 | } elseif (class_exists(CspNonceProvider::class)) { 32 | $nonce = ObjectManager::getInstance() 33 | ->get(CspNonceProvider::class) 34 | ->generateNonce(); 35 | } 36 | 37 | return $nonce; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Model/Report.php: -------------------------------------------------------------------------------- 1 | setApplicationPath($report->getApplicationPath()) 13 | ->throwable($report->getThrowable()) 14 | ->useContext((fn() => $this->context)->call($report)) 15 | ->exceptionClass($report->getExceptionClass()) 16 | ->message($report->getMessage()) 17 | ->stacktrace($report->getStacktrace()) 18 | ->exceptionContext($report->getThrowable()) 19 | ->setApplicationVersion($report->getApplicationVersion()); 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | protected function stracktraceAsArray(): array 26 | { 27 | $result = []; 28 | $applicationFrames = []; 29 | 30 | foreach (parent::stracktraceAsArray() as $frame) { 31 | $frame['application_frame'] = $this->isApplicationFrame($frame); 32 | 33 | if ($frame['application_frame']) { 34 | $applicationFrames[] = $frame; 35 | } 36 | 37 | $result[] = $frame; 38 | } 39 | 40 | if (!array_filter($applicationFrames, fn ($frame) => !$this->isSkippableFrame($frame))) { 41 | $result = array_map(function ($frame) { 42 | $frame['application_frame'] = !$this->isGeneratedCodeFrame($frame); 43 | return $frame; 44 | }, $result); 45 | } 46 | 47 | return $result; 48 | } 49 | 50 | /** 51 | * We don't want to collapse everything from vendor folder 52 | * because we develop the packages in the vendor folder. 53 | * 54 | * Additionally we want to collapse some other folders. 55 | */ 56 | private function isApplicationFrame(array $frame): bool 57 | { 58 | $vendorPaths = [ 59 | '/app/code/Magento/', 60 | '/lib/internal/', 61 | '/vendor/magento/', 62 | '/index.php', 63 | ]; 64 | 65 | foreach ($vendorPaths as $path) { 66 | if (str_contains($frame['file'], $path)) { 67 | return false; 68 | } 69 | } 70 | 71 | if ($this->isGeneratedCodeFrame($frame)) { 72 | return false; 73 | } 74 | 75 | return true; 76 | } 77 | 78 | private function isGeneratedCodeFrame(array $frame): bool 79 | { 80 | return str_contains($frame['file'], '/generated/code/'); 81 | } 82 | 83 | private function isSkippableFrame(array $frame): bool 84 | { 85 | return str_starts_with($frame['method'], 'around') 86 | && str_contains($frame['code_snippet'][$frame['line_number']], '$proceed('); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Plugin/App.php: -------------------------------------------------------------------------------- 1 | registerErrorHandler(); 27 | 28 | try { 29 | return $proceed(); 30 | } catch (Throwable $e) { 31 | // Can't rely on Magento's app->catchException in Magento/Framework/App/Bootstrap::run 32 | // because it doesn't handle Throwable types. 33 | $this->handleThrowable($e); 34 | } 35 | } 36 | 37 | private function registerErrorHandler() 38 | { 39 | $this->ignition = $this->ignitionFactory->create()->register(); 40 | } 41 | 42 | private function handleThrowable(Throwable $exception) 43 | { 44 | ob_start(); 45 | 46 | $this->ignition->handleException($exception); 47 | 48 | $html = str_replace( 49 | '