diff --git a/Bootstrap.php b/Bootstrap.php index 78f3091..bd55c23 100644 --- a/Bootstrap.php +++ b/Bootstrap.php @@ -1,17 +1,18 @@ updateDsnAlways(); + } + } catch (\Exception $e) { + Shop::Container()->getLogService()->error('Sentry-SDK: '.$e->getMessage()); + } } -} + + /** + * Executed on plugin installation + * @return void + */ + public function installed(): void + { + parent::installed(); + try { + // Add the sentry dependency + $this->composerRequireSentry(); + // Add the Sentry DSN to the globalinclude.php file + $this->sentryDsnInclude(); + } catch (\Exception $e) { + Shop::Container()->getLogService()->error($e->getMessage()); + } + } + + private function updateDsnAlways(): void + { + $sentryDsnInstalled = $this->isSentryDsnInstalled(); + $sentryDsn = $this->getPlugin()->getConfig()->getValue('sentry_sdk_dsn'); + + // Skip if the Sentry DSN is not set + if(isset($sentryDsn) === false || empty($sentryDsn)) { + return; + } + + if($sentryDsnInstalled) { + // Update the Sentry DSN in the globalinclude.php file when it is not installed + $file = file_get_contents($this->getIncludeFilePath()); + if(strpos($file, $sentryDsn) === false) { + $this->updateSentryDsn($sentryDsn); + } + } else { + // Add the Sentry DSN to the globalinclude.php file if it is not installed + $this->updateSentryDsn($sentryDsn); + } + } + + /** + * Executed on plugin uninstallation + * @param bool $deleteData + * @return void + */ + public function uninstalled(bool $deleteData = true): void + { + parent::uninstalled($deleteData); + try { + // Remove the sentry dependency + $this->composerRequireSentry(true); + // Remove the Sentry DSN from the globalinclude.php file + $this->sentryDsnInclude(true); + } catch (\Exception $e) { + Shop::Container()->getLogService()->error($e->getMessage()); + } + } + + /** + * Update the Sentry DSN in the globalinclude.php file of the shop. It + * returns false if the file does not exist or the DSN could not be updated. + * @param string $sentryDsn The Sentry DSN to update + * @return int|false The number of bytes that were written to the file or false + */ + private function updateSentryDsn(string $sentryDsn) + { + $this->removeLastLineOfSentryDsn(); + $content = sprintf("try { \Sentry\init(['dsn' => '%s']); } catch(\Exception \$e) { Shop::Container()->getLogService()->error('Sentry-SDK: '.\$e->getMessage()); }", $sentryDsn); + return file_put_contents($this->getIncludeFilePath(), $content, FILE_APPEND); + } + + /** + * Remove the last line of the Sentry DSN in the globalinclude.php file + * @return bool True if the last line was removed, false otherwise + */ + private function removeLastLineOfSentryDsn(): bool + { + if($this->isSentryDsnInstalled()) { + $lines = file($this->getIncludeFilePath()); + $last = sizeof($lines) - 1 ; + unset($lines[$last]); + file_put_contents($this->getIncludeFilePath(), $lines); + } + return false; + } + + /** + * Install or uninstall the Sentry DSN in the globalinclude.php file. + * The installation is done by writing the Sentry DSN to the globalinclude.php + */ + private function sentryDsnInclude(bool $uninstall = false) + { + $filePath = $this->getIncludeFilePath(); + if($filePath !== false) { + if($uninstall) { + $this->removeLastLineOfSentryDsn(); + } else { + $this->updateSentryDsn('https://localhost@localhost/0'); + } + } + } + + /** + * Check if the Sentry DSN is installed in the globalinclude.php file + * @return bool True if the Sentry DSN is installed in the include, false otherwise + */ + private function isSentryDsnInstalled(): bool { + $filePath = $this->getIncludeFilePath(); + $searchString = "try { \Sentry\init("; + if($filePath !== false) { + $content = file_get_contents($filePath); + return strpos($content, $searchString) !== false; + } else { + return false; + } + } + + /** + * Get the path of the globalinclude.php file of the shop installation. + * The path is a realpath, so it is a valid path or it returns false if the + * file does not exist. This file is called each time the shop is loaded and + * automatically includes the composer autoload. + * @return string|bool The path of the globalinclude.php file or false if it does not exist + */ + private function getIncludeFilePath(): string|bool + { + $file = 'globalinclude.php'; + if(file_exists(sprintf('%s/%s', $this->getIncludeFolderPath(), $file))) { + return sprintf('%s/%s', $this->getIncludeFolderPath(), $file); + } + return false; + } + + /** + * Get the includes folder of the shop installation. + * The path is a realpath, so it is a valid path or + * it returns false if the folder does not exist. + * @return string|bool + */ + private function getIncludeFolderPath(): string|bool { + $folder = __DIR__.'/../../includes'; + return realpath($folder); + } + + /** + * Install or uninstall the sentry/sentry composer dependency + * in the includes folder of the shop, because the plugin is not loaded in the + * early stages of the shop lifecycle. But for exception interception, the + * sentry/sentry package is needed as soon as possible to submit exceptions. + * @param bool $uninstall If true, the package will be uninstalled + * @return void + */ + private function composerRequireSentry(bool $uninstall = false): void { + if(($folder = $this->getIncludeFolderPath()) !== false) { + $output = []; + $return = -1; + // Run the composer require command + chdir($folder); + if($uninstall) { + exec('composer remove sentry/sentry', $output, $return); + } else { + exec('composer require sentry/sentry', $output, $return); + } + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index 7ee9879..612c7a1 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,5 @@ -# Entwicklungsvorlage für Plugins +# Sentry Loader für den JTL Shop -Für Fragen zur Nutzung des Plugins kann man sich an mich per E-Mail wenden: +> **Hinweis:** Dieses Plugin ist nur für Entwickler gedacht und sollte nur in einer Testumgebung verwendet werden. Im Produktivbetrieb sollte das Plugin nicht verwendet sein, da das Plugin nicht vollständig getestet wurde. -- Dennis Heinrich - -## Mitgelieferte Features - -Diese Vorlage kommt mit folgenden Technologien: - -- Bootstrap Klasse -- Webpack Bundler - - TypeScript - - SCSS Loader - - TailwindCSS - -### Was ist Webpack? - -Webpack ist ein Bundler, der alle Dateien (JavaScript, CSS und ggf. Bilder) in eine einzelne Datei -bündelt: Das Resultat ist das Bundle. Dabei werden alle SCSS Dateien in CSS Dateien kompiliert. -Der Vorteil darin ist, dass Anfragen an den Shop gespart werden und die Seitengeschwindigkeit erhöht wird. - -### Was ist TailwindCSS? - -TailwindCSS ist ein CSS-Framework, das es ermöglicht, CSS direkt im HTML zu schreiben (ohne dabei Inline) -zu sein. Es ist sehr flexibel und kann mit SCSS kombiniert werden und ist eine moderne Alternative zu -überladenen CSS-Frameworks wie Bootstrap, denn Tailwind kompiliert ausschließlich die verwendeten Klassen -und nicht das gesamte Framework in das Webpack Bundle. - -Wenn man mit dem Umgang nicht vertraut ist, kann das einfach ignoriert werden, denn wie bereits erwähnt wurde, -werden nur benutzte Klassen kompiliert und erhöht nicht die Dateigröße vom Webpack Bundle. - -### Was ist TypeScript? - -TypeScript ist eine Programmiersprache, die auf JavaScript basiert. Sie bietet die Möglichkeit, Typen zu definieren, -was die Entwicklung qualitativ aufwerten kann. Statt .js Dateien werden .ts Dateien verwendet, die dann in .js Dateien -kompiliert werden. - -TypeScript zu schreiben ist optional und kann auch ignoriert werden, denn man kann auch reines JavaScript in die Datei -schreiben. Dennoch muss nach dem Ändern der Datei das Webpack Bundle neu gebaut werden. - -## Entwicklung starten - -### Namespace anpassen - -Das Plugin benötigt einen einmaligen Namespace. Dieser muss in auch der Datei `./plugin.xml` angepasst werden. -Die einfachste Lösung ist, im gesamtem Plugin nach `plugin_test` zu suchen und durch den gewünschten Namespace zu -ersetzen, z.B. `meine_firma_plugin_fuer_funktion_xy`. Gängige Praxis ist es, ein Präfix zu verwenden, das allen -eigenen Plugins vorangestellt wird, z.B. `meine_firma` und anschließend der technische Name des Plugins, z.B. `plugin_fuer_funktion_xy`. - -### README.md anpassen - -Die Datei `README.md` ist die Datei, die auf GitHub und im Plugin unter dem Tab "Dokumentation" angezeigt wird. -Sie sollte mit Informationen zum Plugin angepasst werden, damit Nutzer die Funktion des Plugins verstehen. - -### Webpack Bundle bauen - -Damit das Webpack Bundle gebaut werden kann, müssen die Projekt-Voraussetzungen erfüllt sein. Dann muss die -Kommandozeile im Ordner (`./frontend/webpack`) geöffnet werden. - -Um die Abhängigkeiten für Webpack zu installieren, müssen folgende Befehle ausgeführt werden: -```shell -npm install # Für Nutzer, die npm nutzen -pnpm install # Für Nutzer, die pnpm nutzen -``` - -Wenn nun entwickelt wird, muss bei sämtlichen Änderungen das Webpack Bundle neu gebaut werden. Dafür gibt es zwei -verschiedene Befehle: einen um das Bundle einmalig zu bauen und einen um das Bundle bei Änderungen automatisch neu -zu bauen. Bei der automatischen Variante muss die Kommandozeile offen bleiben. - -Für das einmalige Bauen des Bundles: -```shell -npm run build # Für Nutzer, die npm nutzen -pnpm run build # Für Nutzer, die pnpm nutzen -``` - -Für das automatische Bauen nach Änderung einer Datei: -```shell -npm run watch # Für Nutzer, die npm nutzen -pnpm run watch # Für Nutzer, die pnpm nutzen -``` - -### Veröffentlichung eines Plugins (Workflow) - -Wenn das Plugin fertig entwickelt wurde, kann es veröffentlicht werden. Dazu gehört u.a. das abschließende -Bauen des Webpack Bundles und das Erstellen eines ZIP-Archivs. Um dem Entwickler eine -einfache Möglichkeit zu geben, Versionen zu erstellen und zu veröffentlichen, gibt es ein GitHub-Workflow. - -Der Workflow wird automatisch nach einem Push auf GitHub ausgeführt, wenn ein neuer Tag mit Git erstellt wurde. So kann man -wenn man eine neue Version erstellt hat, einen Tag mit der Versionsnummer erstellen und alle Schritte werden automatisch -ausgeführt. Das Plugin bekommt dann unter dem Tab "Releases" auf GitHub einen Eintrag mit der Versionsnummer und dem Link zum -Download des fertigen Plugins. Das erstellen eines Tags kann mit folgendem Befehl ausgeführt werden: - -```shell -git tag 1.0.0 # Für die Version 1.0.0 -git push --tags # Tags auf GitHub hochladen -``` - -## Voraussetzungen - -- NodeJS mit `npm` oder `pnpm` -- JTL Shop auf 5.0.0 oder höher -- PHP 7.4 oder höher \ No newline at end of file +Dieses Plugin lädt den Sentry SDK in den JTL Shop. Der Sentry SDK ist ein Open Source Projekt, welches es ermöglicht, Fehler und Ausnahmen in der Anwendung zu erfassen und an Sentry zu übermitteln. Als Alternative zu diesem Plugin kann auch das Open-Source Projekt GlitchTip verwendet werden. \ No newline at end of file diff --git a/info.xml b/info.xml index 4691be9..568ab8f 100644 --- a/info.xml +++ b/info.xml @@ -1,22 +1,25 @@ - Vorlage eines Plugins - Hier könnte Ihre Beschreibung stehen. + Sentry-SDK Loader + Automatische Fehlerübermittlung zur Nachverfolgung. Dennis Heinrich https://dennis-heinri.ch - plugin_test + dh_sentry_loader 100 - 1.0.0 + 1.0.1 5.0.0 - 2023-02-01 + 2024-05-07 - - - main.js - 1 - head - - + + + Einstellungen + + Sentry-DSN + Die URL zur Sentry DSN + sentry_sdk_dsn + + +