diff --git a/.gitattributes b/.gitattributes index b5d42df..c2b4bb0 100644 --- a/.gitattributes +++ b/.gitattributes @@ -37,7 +37,3 @@ rector.php export-ignore user_guide_src/ export-ignore .nojekyll export-ignore phpdoc.dist.xml export-ignore - -# deprecated folders - not part of the library -Wizdam_DEPRICATED/ export-ignore -src_DEPRICATED/ export-ignore diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 782a220..e9aabce 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: [Rochmady] +github: [mokesano] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..14206b4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "composer" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml new file mode 100644 index 0000000..1cf1885 --- /dev/null +++ b/.github/workflows/codacy.yml @@ -0,0 +1,61 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow checks out code, performs a Codacy security scan +# and integrates the results with the +# GitHub Advanced Security code scanning feature. For more information on +# the Codacy security scan action usage and parameters, see +# https://github.com/codacy/codacy-analysis-cli-action. +# For more information on Codacy Analysis CLI in general, see +# https://github.com/codacy/codacy-analysis-cli. + +name: Codacy Security Scan + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '33 18 * * 1' + +permissions: + contents: read + +jobs: + codacy-security-scan: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Codacy Security Scan + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout code + uses: actions/checkout@v4 + + # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis + - name: Run Codacy Analysis CLI + uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b + with: + # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository + # You can also omit the token and run the tools that support default configurations + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + verbose: true + output: results.sarif + format: sarif + # Adjust severity of non-security issues + gh-code-scanning-compat: true + # Force 0 exit code to allow SARIF file generation + # This will handover control about PR rejection to the GitHub side + max-allowed-issues: 2147483647 + + # Upload the SARIF file generated in the previous step + - name: Upload SARIF results file + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif diff --git a/.github/workflows/phpmd.yml b/.github/workflows/phpmd.yml new file mode 100644 index 0000000..382178d --- /dev/null +++ b/.github/workflows/phpmd.yml @@ -0,0 +1,57 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# PHPMD is a spin-off project of PHP Depend and +# aims to be a PHP equivalent of the well known Java tool PMD. +# What PHPMD does is: It takes a given PHP source code base +# and look for several potential problems within that source. +# These problems can be things like: +# Possible bugs +# Suboptimal code +# Overcomplicated expressions +# Unused parameters, methods, properties +# More details at https://phpmd.org/ + +name: PHPMD + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '33 17 * * 6' + +permissions: + contents: read + +jobs: + PHPMD: + name: Run PHPMD scanning + runs-on: ubuntu-latest + permissions: + contents: read # for checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@aa1fe473f9c687b6fb896056d771232c0bc41161 + with: + coverage: none + tools: phpmd + + - name: Run PHPMD + run: phpmd . sarif codesize --reportfile phpmd-results.sarif + continue-on-error: true + + - name: Upload analysis results to GitHub + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: phpmd-results.sarif + wait-for-processing: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..76140fa --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,55 @@ +name: Release Otomatis (Semantic Release) + +on: + push: + branches: + - main + - master + workflow_dispatch: + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install semantic-release dan plugin + run: | + npm install --no-save \ + semantic-release \ + @semantic-release/commit-analyzer \ + @semantic-release/release-notes-generator \ + @semantic-release/github + + - name: Generate release config (GitHub release only) + run: | + cat > .releaserc.json < + PHP Version + License + Packagist + Build Status +

--- -## Daftar Isi - -- [Fitur](#fitur) -- [Persyaratan](#persyaratan) -- [Instalasi](#instalasi) -- [Struktur Direktori](#struktur-direktori) -- [Cara Penggunaan](#cara-penggunaan) - - [Inisialisasi Dasar](#inisialisasi-dasar) - - [Integrasi OJS (Output Buffering)](#1-integrasi-ojs-output-buffering) - - [Integrasi Database ADODB](#2-integrasi-database-adodb) - - [Integrasi PSR-15 Middleware](#3-integrasi-psr-15-middleware) -- [Collectors](#collectors) -- [Adapters](#adapters) -- [Konfigurasi](#konfigurasi) -- [Menambah Collector Baru](#menambah-collector-baru) -- [Kompatibilitas Framework](#kompatibilitas-framework) -- [Troubleshooting](#troubleshooting) -- [Atribusi & Lisensi](#atribusi--lisensi) - ---- +## โœจ Mengapa Debug Toolbar - Wizdam? -## Fitur - -- **Framework-agnostic** โ€” tidak bergantung pada CI4, Laravel, Slim, atau framework apapun -- **10 Collectors Built-in:** - - โฑ๏ธ **Timers** โ€” Benchmark & timeline eksekusi kode - - ๐Ÿ’พ **Database** โ€” Query logging dengan support ADODB, PDO, Doctrine - - ๐Ÿ›ฃ๏ธ **Routes** โ€” Inspector route (page, op, parameters) - - ๐Ÿ“„ **Views** โ€” Template render tracker dengan durasi - - ๐Ÿ“ **Files** โ€” Daftar file yang di-include/required - - ๐Ÿ”ฅ **Events** โ€” Event listener & trigger tracker - - ๐Ÿ“ **Logs** โ€” PSR-3 log viewer - - โš™๏ธ **Config** โ€” Informasi konfigurasi aplikasi - - ๐Ÿ“œ **History** โ€” Riwayat N request terakhir -- **Dua mode integrasi:** - - Output buffering (untuk OJS/legacy) - - PSR-15 middleware (untuk aplikasi modern) -- **Adapters built-in:** - - `AdodbDatabaseAdapter` โ€” Integrasi native dengan ADODB - - `WizdamRouterAdapter` โ€” Integrasi dengan Wizdam Router -- **Interfaces untuk ekstensibilitas:** - - `DatabaseAdapterInterface`, `RouterInterface`, `TemplateEngineInterface`, `CollectorInterface` -- **UI Modern:** - - Dark mode otomatis (mengikuti preferensi sistem) - - Responsive design - - AJAX-based history navigation - - Real-time toolbar injection -- **PHP 8.0โ€“8.4 compatible** โ€” diuji di PHP 8.4 dengan OJS 2.4.8.5 +| Situasi | Solusi | +| :--- | :--- | +| Anda ingin melihat query database yang lambat di aplikasi legacy OJS. | Aktifkan toolbar, dan *DatabaseCollector* akan mencatat semua query ADODB beserta durasinya. | +| Anda penasaran *view* atau *template* mana yang paling lama dirender. | *ViewsCollector* menampilkan daftar view dan waktu rendering masing-masing. | +| Anda perlu tahu rute mana yang *match* dengan request saat ini. | *RoutesCollector* akan menunjukkan rute, *controller*, dan parameter. | +| Anda ingin memantau *event* yang di-*trigger* selama request. | *EventsCollector* menyediakan timeline event. | +| Toolbar harus bekerja tanpa mengganggu framework utama. | Dirancang *framework-agnostic*, bisa diintegrasikan via *output buffering* atau *middleware*. | --- -## Persyaratan +## ๐Ÿ”ง Instalasi -| Komponen | Versi Minimum | -|:---|:---| -| PHP | 8.0 | -| Composer | 2.x | -| Browser | Chrome 90+, Firefox 88+, Safari 14+ | - -Tidak ada dependensi Composer yang wajib. Dependensi opsional: -- `psr/http-message` โ€” untuk integrasi PSR-7 request/response -- `psr/simple-cache` โ€” untuk history storage berbasis PSR-16 - ---- - -## Instalasi - -### Via Composer (direkomendasikan) +### Via Composer (Direkomendasikan) ```bash -composer require wizdamdebug/debug-toolbar -``` - -### Dari repository (development) - -```bash -# Tambahkan repository ke composer.json proyek Anda -composer config repositories.wizdam-debug-toolbar vcs https://github.com/mokesano/wizdam-debug-toolbar.git - -# Install versi development -composer require wizdamdebug/debug-toolbar:@dev -``` - -### Manual (tanpa Composer) - -1. Download atau clone repository ini -2. Salin folder `src/`, `config/`, `public/`, dan `views/` ke direktori library proyek Anda -3. Daftarkan namespace `WizdamDebugToolbar\` ke autoloader Anda: - -```php -// Di file bootstrap/autoload manual -spl_autoload_register(function (string $class): void { - $prefix = 'WizdamDebugToolbar\\'; - $base = __DIR__ . '/libs/wizdam-debug-toolbar/src/'; - - if (str_starts_with($class, $prefix)) { - $file = $base . str_replace('\\', '/', substr($class, strlen($prefix))) . '.php'; - if (file_exists($file)) { - require $file; - } - } -}); +composer require wizdam/debug-toolbar ``` ---- +### Integrasi ke Proyek Development -## Struktur Direktori +Tambahkan repository ke `composer.json` proyek Anda: -``` -wizdam-debug-toolbar/ -โ”œโ”€โ”€ src/ -โ”‚ โ”œโ”€โ”€ DebugToolbar.php # Engine utama (~20KB) -โ”‚ โ”œโ”€โ”€ Middleware/ -โ”‚ โ”‚ โ””โ”€โ”€ DebugToolbarMiddleware.php # PSR-15 + output buffering -โ”‚ โ”œโ”€โ”€ Collectors/ # 10 collectors -โ”‚ โ”‚ โ”œโ”€โ”€ BaseCollector.php -โ”‚ โ”‚ โ”œโ”€โ”€ TimersCollector.php -โ”‚ โ”‚ โ”œโ”€โ”€ DatabaseCollector.php -โ”‚ โ”‚ โ”œโ”€โ”€ RoutesCollector.php -โ”‚ โ”‚ โ”œโ”€โ”€ ViewsCollector.php -โ”‚ โ”‚ โ”œโ”€โ”€ FilesCollector.php -โ”‚ โ”‚ โ”œโ”€โ”€ EventsCollector.php -โ”‚ โ”‚ โ”œโ”€โ”€ LogsCollector.php -โ”‚ โ”‚ โ”œโ”€โ”€ ConfigCollector.php -โ”‚ โ”‚ โ””โ”€โ”€ HistoryCollector.php -โ”‚ โ”œโ”€โ”€ Adapters/ # 2 adapters -โ”‚ โ”‚ โ”œโ”€โ”€ AdodbDatabaseAdapter.php # Untuk OJS / ADODB -โ”‚ โ”‚ โ””โ”€โ”€ WizdamRouterAdapter.php # Untuk Wizdam Router -โ”‚ โ””โ”€โ”€ Interfaces/ # 4 interfaces -โ”‚ โ”œโ”€โ”€ CollectorInterface.php -โ”‚ โ”œโ”€โ”€ DatabaseAdapterInterface.php -โ”‚ โ”œโ”€โ”€ RouterInterface.php -โ”‚ โ””โ”€โ”€ TemplateEngineInterface.php -โ”œโ”€โ”€ config/ -โ”‚ โ””โ”€โ”€ wizdamtoolbar.php # File konfigurasi -โ”œโ”€โ”€ public/ -โ”‚ โ”œโ”€โ”€ toolbar.css # ~19KB -โ”‚ โ”œโ”€โ”€ toolbar.js # ~29KB -โ”‚ โ”œโ”€โ”€ toolbarloader.js # ~4KB -โ”‚ โ””โ”€โ”€ toolbarstandalone.js # ~2KB -โ”œโ”€โ”€ views/ -โ”‚ โ”œโ”€โ”€ toolbar.tpl.php # Main template -โ”‚ โ”œโ”€โ”€ _config.tpl -โ”‚ โ”œโ”€โ”€ _database.tpl -โ”‚ โ”œโ”€โ”€ _events.tpl -โ”‚ โ”œโ”€โ”€ _files.tpl -โ”‚ โ”œโ”€โ”€ _history.tpl -โ”‚ โ”œโ”€โ”€ _logs.tpl -โ”‚ โ””โ”€โ”€ _routes.tpl -โ”œโ”€โ”€ composer.json -โ”œโ”€โ”€ README.md -โ”œโ”€โ”€ LICENSE -โ””โ”€โ”€ SECURITY.md +```bash +composer config repositories.wizdam-debug-toolbar vcs https://github.com/mokesano/DebugToolbar.git +composer require wizdam/debug-toolbar:@dev ``` -**Catatan:** Folder `Wizdam_DEPRICATED/` dan `src_DEPRICATED/` tidak termasuk dalam distribusi package. +> **Dependensi opsional**: `psr/http-message` (untuk integrasi PSR-7) dan `psr/simple-cache` (untuk *history storage*). Tidak ada dependensi wajib lainnya. --- -## Cara Penggunaan +## โšก Contoh Penggunaan ### Inisialisasi Dasar @@ -181,79 +56,24 @@ $toolbar = new DebugToolbar($config); $toolbar->run(); ``` -### 1. Integrasi OJS (Output Buffering) +### Integrasi Aplikasi Legacy (Output Buffering) -Cara paling sederhana untuk OJS 2.4.8.5 atau aplikasi PHP legacy apapun. -Tambahkan tiga baris di file bootstrap utama aplikasi Anda (misalnya `index.php`): +Cocok untuk aplikasi PHP tanpa *middleware stack*. ```php -startBuffer(); // mulai menangkap output -} +$middleware = new \WizdamDebugToolbar\Middleware\DebugToolbarMiddleware($toolbar); +$middleware->startBuffer(); -// ... sisa kode bootstrap OJS berjalan normal ... +// ... logika aplikasi Anda berjalan normal ... -// Di bagian PALING BAWAH index.php, setelah semua output selesai -if (defined('WIZDAM_DEBUG') && WIZDAM_DEBUG === true) { - $middleware->endBuffer(); // inject toolbar & flush output -} +$middleware->endBuffer(); // Toolbar otomatis di-inject di akhir HTML ``` -Definisikan konstanta di file konfigurasi environment Anda: +### Integrasi Database ADODB -```php -// config/environment.php atau .env handler -define('WIZDAM_DEBUG', true); // set false di production -``` - -> **Penting:** Jangan pernah mengaktifkan toolbar di environment production. -> Toolbar menampilkan informasi sensitif seperti query database, konfigurasi server, dan path file. - ---- - -### 2. Integrasi Database ADODB - -`AdodbDatabaseAdapter` menggunakan pola static accumulator karena ADODB tidak memiliki event hook bawaan. Ada dua cara mengintegrasikannya: - -#### Cara A โ€” Subclass ADOConnection (direkomendasikan) - -Buat wrapper tipis di atas koneksi ADODB OJS: - -```php -addCollector(new DatabaseCollector($dbAdapter)); ``` -#### Cara B โ€” Logging manual (untuk kasus khusus) - -```php -use WizdamDebugToolbar\Adapters\AdodbDatabaseAdapter; - -$start = microtime(true); -$result = $dbconn->Execute($sql, $params); -$ms = (microtime(true) - $start) * 1000; - -AdodbDatabaseAdapter::logQuery($sql, $ms, $params ?? []); -``` - ---- - -### 3. Integrasi PSR-15 Middleware +### Integrasi PSR-15 Middleware -Untuk aplikasi modern yang sudah memiliki stack middleware PSR-15: +Untuk aplikasi modern yang sudah memiliki *middleware stack*: ```php -use WizdamDebugToolbar\DebugToolbar; -use WizdamDebugToolbar\Middleware\DebugToolbarMiddleware; - -// Inisialisasi -$config = require 'config/wizdamtoolbar.php'; -$toolbar = new DebugToolbar($config); - -// Tambahkan ke stack middleware PSR-15 -$app->add(new DebugToolbarMiddleware($toolbar)); -``` - -Atau gunakan mode `process()` manual: - -```php -$config = require 'config/wizdamtoolbar.php'; -$toolbar = new DebugToolbar($config); -$middleware = new DebugToolbarMiddleware($toolbar); - -$htmlOutput = $middleware->process($_REQUEST, function (array $request): string { - // handler aplikasi Anda โ€” harus return string HTML - return $myApp->handle($request); -}); - -echo $htmlOutput; +$app->add(new \WizdamDebugToolbar\Middleware\DebugToolbarMiddleware($toolbar)); ``` --- -## Collectors - -Collector adalah kelas yang mengumpulkan data tertentu untuk ditampilkan di toolbar. - -| Collector | Keterangan | Dependency | -|:---|:---|:---| -| `TimersCollector` | Benchmark / timeline eksekusi | Tidak ada | -| `DatabaseCollector` | Query log, durasi, duplikat | `DatabaseAdapterInterface` | -| `RoutesCollector` | Route saat ini, controller, params | `RouterInterface` | -| `ViewsCollector` | Template yang di-render, durasi render | `TemplateEngineInterface` | -| `FilesCollector` | File yang di-load, penggunaan memori | Tidak ada | -| `EventsCollector` | Timeline event listener | Tidak ada | -| `LogsCollector` | Output logger (PSR-3 compatible) | Tidak ada | -| `ConfigCollector` | Nilai konfigurasi dan ENV vars | Tidak ada | -| `HistoryCollector` | Riwayat N request terakhir | PSR-16 / file storage | +## ๐Ÿงฉ Fitur Utama -### Menambah atau menonaktifkan collector - -```php -use WizdamDebugToolbar\DebugToolbar; -use WizdamDebugToolbar\Collectors\TimersCollector; -use WizdamDebugToolbar\Collectors\DatabaseCollector; - -$config = [ - 'collectors' => [ - TimersCollector::class, - DatabaseCollector::class, - // tambahkan hanya yang Anda butuhkan - ], -]; - -$toolbar = new DebugToolbar($config); -``` +| Kategori | Kolektor / Fitur | +| :--- | :--- | +| โฑ๏ธ **Timers** | Benchmark & timeline eksekusi kode | +| ๐Ÿ—„๏ธ **Database** | Query logging (ADODB, PDO, Doctrine) | +| ๐Ÿ›ฃ๏ธ **Routes** | Inspector rute, *controller*, parameter | +| ๐Ÿ“„ **Views** | Template *render tracker* dengan durasi | +| ๐Ÿ“ **Files** | Daftar file yang di-*include* / *require* | +| ๐Ÿ“ก **Events** | *Event listener* & *trigger tracker* | +| ๐Ÿ“‹ **Logs** | PSR-3 *log viewer* | +| โš™๏ธ **Config** | Informasi konfigurasi aplikasi | +| ๐Ÿ“œ **History** | Riwayat N *request* terakhir | --- -## Adapters - -Adapter menghubungkan collector dengan implementasi spesifik framework atau library. - -### Database - -| Adapter | Target | Status | -|:---|:---|:---| -| `AdodbDatabaseAdapter` | OJS 2.4.8.5 / ADODB | โœ… Tersedia | -| `PdoDatabaseAdapter` | Aplikasi berbasis PDO | ๐Ÿ”ง Planned | -| `DoctrineAdapter` | Symfony / Doctrine ORM | ๐Ÿ”ง Planned | - -### Router +## โš™๏ธ Konfigurasi -| Adapter | Target | Status | -|:---|:---|:---| -| `WizdamRouterAdapter` | Wizdam Router | โœ… Tersedia | -| `SlimRouterAdapter` | Slim Framework 4 | ๐Ÿ”ง Planned | -| `LaravelRouterAdapter` | Laravel 10+ | ๐Ÿ”ง Planned | - -### Membuat adapter sendiri - -Implementasikan interface yang sesuai: +Salin dan sesuaikan `config/wizdamtoolbar.php`. Beberapa opsi penting: ```php -getQueries()); - } - - public function getDuplicates(): array - { - // deteksi query yang dijalankan lebih dari satu kali - $counts = array_count_values( - array_column($this->getQueries(), 'sql') - ); - return array_filter($counts, fn($c) => $c > 1); - } -} -``` - ---- - -## Konfigurasi - -Salin dan sesuaikan file `config/wizdamtoolbar.php`: - -```php - [ - \WizdamDebugToolbar\Collectors\TimersCollector::class, - \WizdamDebugToolbar\Collectors\DatabaseCollector::class, - \WizdamDebugToolbar\Collectors\RoutesCollector::class, - \WizdamDebugToolbar\Collectors\FilesCollector::class, - \WizdamDebugToolbar\Collectors\EventsCollector::class, - \WizdamDebugToolbar\Collectors\LogsCollector::class, - \WizdamDebugToolbar\Collectors\ConfigCollector::class, - \WizdamDebugToolbar\Collectors\HistoryCollector::class, - ], - - // Jumlah maksimum riwayat request yang disimpan - 'maxHistory' => 20, - - // Direktori penyimpanan file history (harus writable) - 'historyPath' => sys_get_temp_dir() . '/wizdam-debug-toolbar/', - - // Route yang tidak di-inject toolbar (regex pattern) - 'ignoredRoutes' => [ - '/_wizdam-debug-toolbar', - '/api/', - ], - - // Tampilan awal toolbar ('minimized' atau 'maximized') - 'toolbarState' => 'minimized', - - // Tema toolbar ('light', 'dark', atau 'auto') - 'theme' => 'auto', - - // Max query time untuk highlighting (ms) - 'maxQueryTime' => 100, + 'collectors' => [ /* ... daftar collector class ... */ ], + 'maxHistory' => 20, + 'historyPath' => sys_get_temp_dir() . '/wizdam-debug-toolbar/', + 'ignoredRoutes'=> ['/_wizdam-debug-toolbar', '/api/'], + 'toolbarState' => 'minimized', // 'minimized' atau 'maximized' + 'theme' => 'auto', // 'light', 'dark', atau 'auto' + 'maxQueryTime' => 100, // Highlight query lambat (ms) ]; ``` --- -## Menambah Collector Baru +## ๐Ÿงช Menambah Collector Kustom -Buat class yang mengimplementasikan `CollectorInterface`: +Implementasikan `CollectorInterface`: ```php - MyCacheDriver::getHits(), - 'misses' => MyCacheDriver::getMisses(), - ]; - } - - public function isEnabled(): bool - { - return class_exists('MyCacheDriver'); - } - - public function getBadgeValue(): string|int|null - { - return MyCacheDriver::getHits() . ' hits'; - } - - public function getIcon(): string - { - return 'cache'; // nama icon dari set toolbar - } + public function getData(): array { /* ... */ } + public function isEnabled(): bool { /* ... */ } + public function getBadgeValue(): string|int|null { /* ... */ } + public function getIcon(): string { /* ... */ } } ``` Daftarkan ke toolbar: ```php -use WizdamDebugToolbar\DebugToolbar; -use MyApp\Collectors\CacheCollector; - -$config = require 'config/wizdamtoolbar.php'; $config['collectors'][] = CacheCollector::class; - $toolbar = new DebugToolbar($config); ``` --- -## Troubleshooting - -### Toolbar tidak muncul - -1. **Pastikan `WIZDAM_DEBUG` didefinisikan sebagai `true`** - ```php - define('WIZDAM_DEBUG', true); - ``` - -2. **Cek apakah output buffering aktif** - Pastikan `startBuffer()` dipanggil sebelum ada output HTML dan `endBuffer()` dipanggil setelah semua output selesai. - -3. **Periksa ignored routes** - Jika URL Anda match dengan pattern di `ignoredRoutes`, toolbar tidak akan di-inject. - -4. **Cek browser console untuk error JavaScript** - Tekan F12 > Console dan cari error terkait `toolbar.js`. - -### Query database tidak tercatat - -1. **Pastikan adapter sudah didaftarkan** - ```php - $dbAdapter = new AdodbDatabaseAdapter(); - $toolbar->addCollector(new DatabaseCollector($dbAdapter)); - ``` - -2. **Untuk ADODB: Pastikan wrapper class digunakan** - Gunakan `WizdamAdodbConnection` atau logging manual via `AdodbDatabaseAdapter::logQuery()`. - -### Error "Class not found" - -Pastikan autoloader sudah dikonfigurasi dengan benar untuk namespace `WizdamDebugToolbar\`: - -```php -spl_autoload_register(function (string $class): void { - $prefix = 'WizdamDebugToolbar\\'; - // ... sesuaikan path ke folder src/ -}); -``` - -Atau gunakan Composer autoloading: -```bash -composer dump-autoload -``` - -### History tidak tersimpan - -1. **Pastikan direktori `historyPath` writable** - ```php - 'historyPath' => sys_get_temp_dir() . '/wizdam-debug-toolbar/', - ``` - -2. **Cek permission folder** - ```bash - chmod 755 /tmp/wizdam-debug-toolbar - ``` - ---- - -## Kompatibilitas Framework - -| Framework / Platform | Versi | Status | Adapter tersedia | -|:---|:---|:---|:---| -| OJS (Open Journal Systems) | 2.4.8.5 | โœ… Diuji | Database, Router | -| PHP Native / Custom | 8.0โ€“8.4 | โœ… Diuji | โ€” | -| Wizdam Router | Latest | โœ… Diuji | Router | -| Slim Framework | 4.x | ๐Ÿ”ง Planned | โ€” | -| Laravel | 10, 11 | ๐Ÿ”ง Planned | โ€” | -| Symfony | 6, 7 | ๐Ÿ”ง Planned | โ€” | -| CodeIgniter 3 | 3.1.x | ๐Ÿ”ง Planned | โ€” | - ---- - -## Atribusi & Lisensi - -**Wizdam Debug Toolbar** diekstraksi dan direkayasa ulang dari **CodeIgniter4 v4.7.2 Debug Toolbar**, yang dikembangkan oleh [CodeIgniter Foundation](https://codeigniter.com) dan kontributornya. - -### File yang diadaptasi dari CodeIgniter4: -- `src/Collectors/` โ€” berdasarkan `system/Debug/Toolbar/Collectors/` -- `views/` โ€” berdasarkan `system/Debug/Toolbar/Views/` -- `public/` โ€” berdasarkan `system/Debug/Toolbar/` (CSS, JS) - -Semua file tersebut telah dimodifikasi dengan: -- Perubahan namespace dari `CodeIgniter\Debug\Toolbar` ke `WizdamDebugToolbar` -- Penghapusan dependency framework CI4 -- Adaptasi untuk standalone usage - -### File yang dibuat baru (tidak berasal dari CodeIgniter4): -- `src/Interfaces/` โ€” seluruh interface (`CollectorInterface`, `DatabaseAdapterInterface`, `RouterInterface`, `TemplateEngineInterface`) -- `src/Adapters/` โ€” seluruh adapter (`AdodbDatabaseAdapter`, `WizdamRouterAdapter`) -- `src/Middleware/DebugToolbarMiddleware.php` -- `src/DebugToolbar.php` โ€” main engine yang direkayasa ulang -- `config/wizdamtoolbar.php` โ€” konfigurasi standalone - ---- - -### Lisensi +## ๐Ÿ” Troubleshooting -**MIT License** - -Copyright (c) 2025 [Sangia Publishing House](https://www.sangia.org) -Copyright (c) 2014-2024 British Columbia Institute of Technology (CodeIgniter Foundation) - -Lihat file [LICENSE](LICENSE) untuk teks lisensi lengkap. +| Masalah | Solusi | +| :--- | :--- | +| Toolbar tidak muncul | Pastikan `WIZDAM_DEBUG = true` dan *buffer* dimulai sebelum output HTML. | +| Query database tidak tercatat | Pastikan adapter (mis. `AdodbDatabaseAdapter`) sudah didaftarkan ke `DatabaseCollector`. | +| Error "Class not found" | Jalankan `composer dump-autoload` atau periksa konfigurasi *autoloader* manual. | +| History tidak tersimpan | Pastikan direktori `historyPath` *writable* (chmod 755). | --- -## Support & Kontribusi +## ๐Ÿ“„ Lisensi -Untuk pertanyaan, bug report, atau feature request: +**MIT License** โ€” Copyright (c) 2025 Sangia Publishing House. Lihat [LICENSE](https://github.com/mokesano/DebugToolbar/blob/master/LICENSE) untuk teks lengkap. -- ๐Ÿ“ง Email: dev@sangia.org -- ๐Ÿ› Issue Tracker: https://github.com/mokesano/WizdamDebugToolbar/issues -- ๐Ÿ“– Dokumentasi: README.md ini - -Kontribusi sangat diterima! Silakan fork repository dan buat pull request. +> Toolbar ini diadaptasi dari [CodeIgniter 4 Debug Toolbar](https://github.com/codeigniter4/CodeIgniter4) (Copyright British Columbia Institute of Technology). Digunakan dengan modifikasi dan izin sesuai lisensi MIT asli. --- -*Dikembangkan sebagai bagian dari ekosistem **Wizdam Frontedge** โ€” platform penerbitan ilmiah berbasis OJS dengan arsitektur modern.* - -**Happy Debugging! ๐Ÿ›๐Ÿ”** +

+ Dibangun dengan โค๏ธ sebagai bagian dari ekosistem Wizdam Frontedge โ€” platform penerbitan ilmiah modern. +

+ GitHub Stars + GitHub Forks +

diff --git a/SECURITY.md b/SECURITY.md index 87b894e..584b49d 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ The development team and community take all security issues seriously. **Please Thank you for improving the security of our code! Any assistance in removing security flaws will be acknowledged. -**Please report security flaws by emailing the development team directly: security@codeigniter.com**. +**Please report security flaws by emailing the development team directly: security@sangia.org**. The lead maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the @@ -20,7 +20,7 @@ This person will coordinate the fix and release process, involving the following - Confirm the problem and determine the affected versions. - Audit code to find any potential similar problems. - Prepare fixes for all releases still under maintenance. These fixes will be released as fast as possible. -- Publish security advisories at https://github.com/codeigniter4/CodeIgniter4/security/advisories +- Publish security advisories at https://github.com/mokesano/WizdamDebugToolbar/security/advisories ## Comments on this Policy diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/BaseCollector.php b/Wizdam_DEPRICATED/DebugBar/Collectors/BaseCollector.php deleted file mode 100644 index 96fe549..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/BaseCollector.php +++ /dev/null @@ -1,229 +0,0 @@ -title)); - } - - return $this->title; - } - - /** - * Returns any information that should be shown next to the title. - */ - public function getTitleDetails(): string - { - return ''; - } - - /** - * Does this collector need it's own tab? - */ - public function hasTabContent(): bool - { - return (bool) $this->hasTabContent; - } - - /** - * Does this collector have a label? - */ - public function hasLabel(): bool - { - return (bool) $this->hasLabel; - } - - /** - * Does this collector have information for the timeline? - */ - public function hasTimelineData(): bool - { - return (bool) $this->hasTimeline; - } - - /** - * Grabs the data for the timeline, properly formatted, - * or returns an empty array. - */ - public function timelineData(): array - { - if (! $this->hasTimeline) { - return []; - } - - return $this->formatTimelineData(); - } - - /** - * Does this Collector have data that should be shown in the - * 'Vars' tab? - */ - public function hasVarData(): bool - { - return (bool) $this->hasVarData; - } - - /** - * Gets a collection of data that should be shown in the 'Vars' tab. - * The format is an array of sections, each with their own array - * of key/value pairs: - * - * $data = [ - * 'section 1' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * 'section 2' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * ]; - * - * @return array|null - */ - public function getVarData() - { - return null; - } - - /** - * Child classes should implement this to return the timeline data - * formatted for correct usage. - * - * Timeline data should be formatted into arrays that look like: - * - * [ - * 'name' => 'Database::Query', - * 'component' => 'Database', - * 'start' => 10 // milliseconds - * 'duration' => 15 // milliseconds - * ] - */ - protected function formatTimelineData(): array - { - return []; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - * - * @return array|string - */ - public function display() - { - return []; - } - - /** - * This makes nicer looking paths for the error output. - * - * @deprecated Use the dedicated `clean_path()` function. - */ - public function cleanPath(string $file): string - { - return clean_path($file); - } - - /** - * Gets the "badge" value for the button. - * - * @return int|null - */ - public function getBadgeValue() - { - return null; - } - - /** - * Does this collector have any data collected? - * - * If not, then the toolbar button won't get shown. - */ - public function isEmpty(): bool - { - return false; - } - - /** - * Returns the HTML to display the icon. Should either - * be SVG, or a base-64 encoded. - * - * Recommended dimensions are 24px x 24px - */ - public function icon(): string - { - return ''; - } - - /** - * Return settings as an array. - */ - public function getAsArray(): array - { - return [ - 'title' => $this->getTitle(), - 'titleSafe' => $this->getTitle(true), - 'titleDetails' => $this->getTitleDetails(), - 'display' => $this->display(), - 'badgeValue' => $this->getBadgeValue(), - 'isEmpty' => $this->isEmpty(), - 'hasTabContent' => $this->hasTabContent(), - 'hasLabel' => $this->hasLabel(), - 'icon' => $this->icon(), - 'hasTimelineData' => $this->hasTimelineData(), - 'timelineData' => $this->timelineData(), - ]; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/Config.php b/Wizdam_DEPRICATED/DebugBar/Collectors/Config.php deleted file mode 100644 index 8a4fb20..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/Config.php +++ /dev/null @@ -1,28 +0,0 @@ - '', - 'phpVersion' => PHP_VERSION, - 'phpSAPI' => PHP_SAPI, - 'environment' => defined('ENVIRONMENT') ? ENVIRONMENT : 'production', - 'baseURL' => $config['baseURL'] ?? '', - 'timezone' => date_default_timezone_get(), - 'locale' => $config['locale'] ?? 'en', - 'cspEnabled' => $config['cspEnabled'] ?? false, - ]; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/Database.php b/Wizdam_DEPRICATED/DebugBar/Collectors/Database.php deleted file mode 100644 index 20cee25..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/Database.php +++ /dev/null @@ -1,258 +0,0 @@ -connections = $connections; - } - - /** - * The static method used to collect query data. - * - * @param array $query Query data array with keys: query, string, start_time, duration, trace - */ - public static function collectQuery(array $query): void - { - $configMax = 100; // Default max queries - - if (count(static::$queries) < $configMax) { - $queryString = $query['string'] ?? ''; - - $backtrace = $query['trace'] ?? debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - - if (! empty($backtrace)) { - // Simplify backtrace - foreach ($backtrace as &$line) { - if (isset($line['file'])) { - $line['file'] = clean_path($line['file']) . ':' . ($line['line'] ?? ''); - unset($line['line']); - } - } - } - - static::$queries[] = [ - 'query' => $query, - 'string' => $queryString, - 'duplicate' => in_array($queryString, array_column(static::$queries, 'string'), true), - 'trace' => $backtrace, - ]; - } - } - - /** - * Returns timeline data formatted for the toolbar. - * - * @return array The formatted data or an empty array. - */ - protected function formatTimelineData(): array - { - $data = []; - - foreach ($this->connections as $alias => $connection) { - // Connection Time - $data[] = [ - 'name' => 'Connecting to Database: "' . $alias . '"', - 'component' => 'Database', - 'start' => $connection->getConnectStart(), - 'duration' => $connection->getConnectDuration(), - ]; - } - - foreach (static::$queries as $query) { - $data[] = [ - 'name' => 'Query', - 'component' => 'Database', - 'start' => $query['query']->getStartTime(true), - 'duration' => $query['query']->getDuration(), - 'query' => $query['query']->debugToolbarDisplay(), - ]; - } - - return $data; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - */ - public function display(): array - { - return ['queries' => array_map(static function (array $query): array { - $isDuplicate = $query['duplicate'] === true; - - $firstNonSystemLine = ''; - - foreach ($query['trace'] as $index => &$line) { - // simplify file and line - if (isset($line['file'])) { - $line['file'] = clean_path($line['file']) . ':' . $line['line']; - unset($line['line']); - } else { - $line['file'] = '[internal function]'; - } - - // find the first trace line that does not originate from `system/` - if ($firstNonSystemLine === '' && ! str_contains($line['file'], 'SYSTEMPATH')) { - $firstNonSystemLine = $line['file']; - } - - // simplify function call - if (isset($line['class'])) { - $line['function'] = $line['class'] . $line['type'] . $line['function']; - unset($line['class'], $line['type']); - } - - if (strrpos($line['function'], '{closure}') === false) { - $line['function'] .= '()'; - } - - $line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function']; - - // add index numbering padded with nonbreaking space - $indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT); - $indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded); - - $line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4); - } - - return [ - 'hover' => $isDuplicate ? 'This query was called more than once.' : '', - 'class' => $isDuplicate ? 'duplicate' : '', - 'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms', - 'sql' => $query['query']->debugToolbarDisplay(), - 'trace' => $query['trace'], - 'trace-file' => $firstNonSystemLine, - 'qid' => md5($query['query'] . Time::now()->format('0.u00 U')), - ]; - }, static::$queries)]; - } - - /** - * Gets the "badge" value for the button. - */ - public function getBadgeValue(): int - { - return count(static::$queries); - } - - /** - * Information to be displayed next to the title. - * - * @return string The number of queries (in parentheses) or an empty string. - */ - public function getTitleDetails(): string - { - $this->getConnections(); - - $queryCount = count(static::$queries); - $uniqueCount = count(array_filter(static::$queries, static fn ($query): bool => $query['duplicate'] === false)); - $connectionCount = count($this->connections); - - return sprintf( - '(%d total Quer%s, %d %s unique across %d Connection%s)', - $queryCount, - $queryCount > 1 ? 'ies' : 'y', - $uniqueCount, - $uniqueCount > 1 ? 'of them' : '', - $connectionCount, - $connectionCount > 1 ? 's' : '', - ); - } - - /** - * Does this collector have any data collected? - */ - public function isEmpty(): bool - { - return static::$queries === []; - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADMSURBVEhLY6A3YExLSwsA4nIycQDIDIhRWEBqamo/UNF/SjDQjF6ocZgAKPkRiFeEhoYyQ4WIBiA9QAuWAPEHqBAmgLqgHcolGQD1V4DMgHIxwbCxYD+QBqcKINseKo6eWrBioPrtQBq/BcgY5ht0cUIYbBg2AJKkRxCNWkDQgtFUNJwtABr+F6igE8olGQD114HMgHIxAVDyAhA/AlpSA8RYUwoeXAPVex5qHCbIyMgwBCkAuQJIY00huDBUz/mUlBQDqHGjgBjAwAAACexpph6oHSQAAAAASUVORK5CYII='; - } - - /** - * Gets the connections from the database config - */ - private function getConnections(): void - { - $this->connections = \Config\Database::getConnections(); - } - - /** - * Reset collector state for worker mode. - * Clears collected queries between requests. - */ - public function reset(): void - { - static::$queries = []; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/Events.php b/Wizdam_DEPRICATED/DebugBar/Collectors/Events.php deleted file mode 100644 index 38e8705..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/Events.php +++ /dev/null @@ -1,114 +0,0 @@ - 'Event: ' . $info['event'], - 'component' => 'Events', - 'start' => $info['start'], - 'duration' => $info['end'] - $info['start'], - ]; - } - - return $data; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - */ - public function display(): array - { - $data = [ - 'events' => [], - ]; - - foreach (\CodeIgniter\Events\Events::getPerformanceLogs() as $row) { - $key = $row['event']; - - if (! array_key_exists($key, $data['events'])) { - $data['events'][$key] = [ - 'event' => $key, - 'duration' => ($row['end'] - $row['start']) * 1000, - 'count' => 1, - ]; - - continue; - } - - $data['events'][$key]['duration'] += ($row['end'] - $row['start']) * 1000; - $data['events'][$key]['count']++; - } - - foreach ($data['events'] as &$row) { - $row['duration'] = number_format($row['duration'], 2); - } - - return $data; - } - - /** - * Gets the "badge" value for the button. - */ - public function getBadgeValue(): int - { - return count(\CodeIgniter\Events\Events::getPerformanceLogs()); - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEASURBVEhL7ZXNDcIwDIVTsRBH1uDQDdquUA6IM1xgCA6MwJUN2hk6AQzAz0vl0ETUxC5VT3zSU5w81/mRMGZysixbFEVR0jSKNt8geQU9aRpFmp/keX6AbjZ5oB74vsaN5lSzA4tLSjpBFxsjeSuRy4d2mDdQTWU7YLbXTNN05mKyovj5KL6B7q3hoy3KwdZxBlT+Ipz+jPHrBqOIynZgcZonoukb/0ckiTHqNvDXtXEAaygRbaB9FvUTjRUHsIYS0QaSp+Dw6wT4hiTmYHOcYZsdLQ2CbXa4ftuuYR4x9vYZgdb4vsFYUdmABMYeukK9/SUme3KMFQ77+Yfzh8eYF8+orDuDWU5LAAAAAElFTkSuQmCC'; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/Files.php b/Wizdam_DEPRICATED/DebugBar/Collectors/Files.php deleted file mode 100644 index 3f72fc0..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/Files.php +++ /dev/null @@ -1,93 +0,0 @@ - $path, - 'name' => basename($file), - ]; - } else { - $userFiles[] = [ - 'path' => $path, - 'name' => basename($file), - ]; - } - } - - sort($userFiles); - sort($coreFiles); - - return [ - 'coreFiles' => $coreFiles, - 'userFiles' => $userFiles, - ]; - } - - /** - * Displays the number of included files as a badge in the tab button. - */ - public function getBadgeValue(): int - { - return count(get_included_files()); - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGBSURBVEhL7ZQ9S8NQGIVTBQUncfMfCO4uLgoKbuKQOWg+OkXERRE1IAXrIHbVDrqIDuLiJgj+gro7S3dnpfq88b1FMTE3VZx64HBzzvvZWxKnj15QCcPwCD5HUfSWR+JtzgmtsUcQBEva5IIm9SwSu+95CAWbUuy67qBa32ByZEDpIaZYZSZMjjQuPcQUq8yEyYEb8FSerYeQVGbAFzJkX1PyQWLhgCz0BxTCekC1Wp0hsa6yokzhed4oje6Iz6rlJEkyIKfUEFtITVtQdAibn5rMyaYsMS+a5wTv8qeXMhcU16QZbKgl3hbs+L4/pnpdc87MElZgq10p5DxGdq8I7xrvUWUKvG3NbSK7ubngYzdJwSsF7TiOh9VOgfcEz1UayNe3JUPM1RWC5GXYgTfc75B4NBmXJnAtTfpABX0iPvEd9ezALwkplCFXcr9styiNOKc1RRZpaPM9tcqBwlWzGY1qPL9wjqRBgF5BH6j8HWh2S7MHlX8PrmbK+k/8PzjOOzx1D3i1pKTTAAAAAElFTkSuQmCC'; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/History.php b/Wizdam_DEPRICATED/DebugBar/Collectors/History.php deleted file mode 100644 index a946042..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/History.php +++ /dev/null @@ -1,132 +0,0 @@ -= 0 && $counter > $limit) { - unlink($filename); - - continue; - } - - // Get the contents of this specific history request - $contents = file_get_contents($filename); - - $contents = @json_decode($contents); - if (json_last_error() === JSON_ERROR_NONE) { - preg_match('/debugbar_(.*)\.json$/s', $filename, $time); - $time = sprintf('%.6F', $time[1] ?? 0); - - // Debugbar files shown in History Collector - $files[] = [ - 'time' => $time, - 'datetime' => DateTime::createFromFormat('U.u', $time)->format('Y-m-d H:i:s.u'), - 'active' => $time === $current, - 'status' => $contents->vars->response->statusCode, - 'method' => $contents->method, - 'url' => $contents->url, - 'isAJAX' => $contents->isAJAX ? 'Yes' : 'No', - 'contentType' => $contents->vars->response->contentType, - ]; - } - } - - $this->files = $files; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - */ - public function display(): array - { - return ['files' => $this->files]; - } - - /** - * Displays the number of included files as a badge in the tab button. - */ - public function getBadgeValue(): int - { - return count($this->files); - } - - /** - * Return true if there are no history files. - */ - public function isEmpty(): bool - { - return $this->files === []; - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJySURBVEhL3ZU7aJNhGIVTpV6i4qCIgkIHxcXLErS4FBwUFNwiCKGhuTYJGaIgnRoo4qRu6iCiiIuIXXTTIkIpuqoFwaGgonUQlC5KafU5ycmNP0lTdPLA4fu+8573/a4/f6hXpFKpwUwmc9fDfweKbk+n07fgEv33TLSbtt/hvwNFT1PsG/zdTE0Gp+GFfD6/2fbVIxqNrqPIRbjg4t/hY8aztcngfDabHXbKyiiXy2vcrcPH8oDCry2FKDrA+Ar6L01E/ypyXzXaARjDGGcoeNxSDZXE0dHRA5VRE5LJ5CFy5jzJuOX2wHRHRnjbklZ6isQ3tIctBaAd4vlK3jLtkOVWqABBXd47jGHLmjTmSScttQV5J+SjfcUweFQEbsjAas5aqoCLXutJl7vtQsAzpRowYqkBinyCC8Vicb2lOih8zoldd0F8RD7qTFiqAnGrAy8stUAvi/hbqDM+YzkAFrLPdR5ZqoLXsd+Bh5YCIH7JniVdquUWxOPxDfboHhrI5XJ7HHhiqQXox+APe/Qk64+gGYVCYZs8cMpSFQj9JOoFzVqqo7k4HIvFYpscCoAjOmLffUsNUGRaQUwDlmofUa34ecsdgXdcXo4wbakBgiUFafXJV8A4DJ/2UrxUKm3E95H8RbjLcgOJRGILhnmCP+FBy5XvwN2uIPcy1AJvWgqC4xm2aU4Xb3lF4I+Tpyf8hRe5w3J7YLymSeA8Z3nSclv4WLRyFdfOjzrUFX0klJUEtZtntCNc+F69cz/FiDzEPtjzmcUMOr83kDQEX6pAJxJfpL3OX22n01YN7SZCoQnaSdoZ+Jz+PZihH3wt/xlCoT9M6nEtmRSPCQAAAABJRU5ErkJggg=='; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/Logs.php b/Wizdam_DEPRICATED/DebugBar/Collectors/Logs.php deleted file mode 100644 index 4b69f9b..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/Logs.php +++ /dev/null @@ -1,82 +0,0 @@ - - */ - protected $data = []; - - /** - * Returns the data of this collector to be formatted in the toolbar. - * - * @return array{logs: list} - */ - public function display(): array - { - return [ - 'logs' => $this->data, - ]; - } - - /** - * Does this collector actually have any data to display? - */ - public function isEmpty(): bool - { - return $this->data === []; - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACYSURBVEhLYxgFJIHU1FSjtLS0i0D8AYj7gEKMEBkqAaAFF4D4ERCvAFrwH4gDoFIMKSkpFkB+OTEYqgUTACXfA/GqjIwMQyD9H2hRHlQKJFcBEiMGQ7VgAqCBvUgK32dmZspCpagGGNPT0/1BLqeF4bQHQJePpiIwhmrBBEADR1MRfgB0+WgqAmOoFkwANHA0FY0CUgEDAwCQ0PUpNB3kqwAAAABJRU5ErkJggg=='; - } - - /** - * Set logs data manually - * - * @param list $logs - */ - public function setLogs(array $logs): void - { - $this->data = $logs; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/Routes.php b/Wizdam_DEPRICATED/DebugBar/Collectors/Routes.php deleted file mode 100644 index fb98c44..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/Routes.php +++ /dev/null @@ -1,158 +0,0 @@ - - * }>, - * routes: list - * } - * - * @throws ReflectionException - */ - public function display(): array - { - $rawRoutes = service('routes', true); - $router = service('router', null, null, true); - - // Get our parameters - // Closure routes - if (is_callable($router->controllerName())) { - $method = new ReflectionFunction($router->controllerName()); - } else { - try { - $method = new ReflectionMethod($router->controllerName(), $router->methodName()); - } catch (ReflectionException) { - try { - // If we're here, the method doesn't exist - // and is likely calculated in _remap. - $method = new ReflectionMethod($router->controllerName(), '_remap'); - } catch (ReflectionException) { - // If we're here, page cache is returned. The router is not executed. - return [ - 'matchedRoute' => [], - 'routes' => [], - ]; - } - } - } - - $rawParams = $method->getParameters(); - - $params = []; - - foreach ($rawParams as $key => $param) { - $params[] = [ - 'name' => '$' . $param->getName() . ' = ', - 'value' => $router->params()[$key] ?? - ' | default: ' - . var_export( - $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, - true, - ), - ]; - } - - $matchedRoute = [ - [ - 'directory' => $router->directory(), - 'controller' => $router->controllerName(), - 'method' => $router->methodName(), - 'paramCount' => count($router->params()), - 'truePCount' => count($params), - 'params' => $params, - ], - ]; - - // Defined Routes - $routes = []; - - $definedRouteCollector = new DefinedRouteCollector($rawRoutes); - - foreach ($definedRouteCollector->collect() as $route) { - // filter for strings, as callbacks aren't displayable - if ($route['handler'] !== '(Closure)') { - $routes[] = [ - 'method' => strtoupper($route['method']), - 'route' => $route['route'], - 'handler' => $route['handler'], - ]; - } - } - - return [ - 'matchedRoute' => $matchedRoute, - 'routes' => $routes, - ]; - } - - /** - * Returns a count of all the routes in the system. - */ - public function getBadgeValue(): int - { - $rawRoutes = service('routes', true); - - return count($rawRoutes->getRoutes()); - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFDSURBVEhL7ZRNSsNQFIUjVXSiOFEcuQIHDpzpxC0IGYeE/BEInbWlCHEDLsSiuANdhKDjgm6ggtSJ+l25ldrmmTwIgtgDh/t37r1J+16cX0dRFMtpmu5pWAkrvYjjOB7AETzStBFW+inxu3KUJMmhludQpoflS1zXban4LYqiO224h6VLTHr8Z+z8EpIHFF9gG78nDVmW7UgTHKjsCyY98QP+pcq+g8Ku2s8G8X3f3/I8b038WZTp+bO38zxfFd+I6YY6sNUvFlSDk9CRhiAI1jX1I9Cfw7GG1UB8LAuwbU0ZwQnbRDeEN5qqBxZMLtE1ti9LtbREnMIuOXnyIf5rGIb7Wq8HmlZgwYBH7ORTcKH5E4mpjeGt9fBZcHE2GCQ3Vt7oTNPNg+FXLHnSsHkw/FR+Gg2bB8Ptzrst/v6C/wrH+QB+duli6MYJdQAAAABJRU5ErkJggg=='; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/Timers.php b/Wizdam_DEPRICATED/DebugBar/Collectors/Timers.php deleted file mode 100644 index f6a3b4d..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/Timers.php +++ /dev/null @@ -1,76 +0,0 @@ -timers = $timers; - } - - /** - * Child classes should implement this to return the timeline data - * formatted for correct usage. - */ - protected function formatTimelineData(): array - { - $data = []; - - if ($this->timers === null) { - return $data; - } - - foreach ($this->timers as $name => $info) { - if ($name === 'total_execution') { - continue; - } - - $data[] = [ - 'name' => ucwords(str_replace('_', ' ', $name)), - 'component' => 'Timer', - 'start' => $info['start'] ?? 0, - 'duration' => ($info['end'] ?? 0) - ($info['start'] ?? 0), - ]; - } - - return $data; - } -} diff --git a/Wizdam_DEPRICATED/DebugBar/Collectors/Views.php b/Wizdam_DEPRICATED/DebugBar/Collectors/Views.php deleted file mode 100644 index 6479a08..0000000 --- a/Wizdam_DEPRICATED/DebugBar/Collectors/Views.php +++ /dev/null @@ -1,139 +0,0 @@ -viewer ??= service('renderer'); - } - - /** - * Child classes should implement this to return the timeline data - * formatted for correct usage. - */ - protected function formatTimelineData(): array - { - $this->initViewer(); - - $data = []; - - $rows = $this->viewer->getPerformanceData(); - - foreach ($rows as $info) { - $data[] = [ - 'name' => 'View: ' . $info['view'], - 'component' => 'Views', - 'start' => $info['start'], - 'duration' => $info['end'] - $info['start'], - ]; - } - - return $data; - } - - /** - * Gets a collection of data that should be shown in the 'Vars' tab. - * The format is an array of sections, each with their own array - * of key/value pairs: - * - * $data = [ - * 'section 1' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * 'section 2' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * ]; - */ - public function getVarData(): array - { - $this->initViewer(); - - return [ - 'View Data' => $this->viewer->getData(), - ]; - } - - /** - * Returns a count of all views. - */ - public function getBadgeValue(): int - { - $this->initViewer(); - - return count($this->viewer->getPerformanceData()); - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADeSURBVEhL7ZSxDcIwEEWNYA0YgGmgyAaJLTcUaaBzQQEVjMEabBQxAdw53zTHiThEovGTfnE/9rsoRUxhKLOmaa6Uh7X2+UvguLCzVxN1XW9x4EYHzik033Hp3X0LO+DaQG8MDQcuq6qao4qkHuMgQggLvkPLjqh00ZgFDBacMJYFkuwFlH1mshdkZ5JPJERA9JpI6xNCBESvibQ+IURA9JpI6xNCBESvibQ+IURA9DTsuHTOrVFFxixgB/eUFlU8uKJ0eDBFOu/9EvoeKnlJS2/08Tc8NOwQ8sIfMeYFjqKDjdU2sp4AAAAASUVORK5CYII='; - } -} diff --git a/composer.json b/composer.json index 36d54b6..edfae69 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,12 @@ { "name": "wizdamdebug/debug-toolbar", "description": "Standalone Debug Toolbar based on CodeIgniter4 DebugBar - Framework Agnostic", + "version": "1.0.0.0", "type": "library", "license": "MIT", + "config": { + "vendor-dir": "library" + }, "keywords": [ "debugbar", "debug", @@ -14,8 +18,8 @@ ], "authors": [ { - "name": "Sangia Publishing House", - "email": "dev@sangia.org", + "name": "Rochmady", + "email": "srochmady@gmail.com", "homepage": "https://www.sangia.org", "role": "Developer" } @@ -49,8 +53,6 @@ }, "archive": { "exclude": [ - "/Wizdam_DEPRICATED/", - "/src_DEPRICATED/", "/.github/", "/tests/", ".editorconfig", diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..9dd99ee --- /dev/null +++ b/composer.lock @@ -0,0 +1,1703 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "853cb7f8b7002b1a584a90ec75c1a82a", + "packages": [], + "packages-dev": [ + { + "name": "myclabs/deep-copy", + "version": "1.13.4", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "doctrine/collections": "<1.6.8", + "doctrine/common": "<2.13.3 || >=3 <3.2.2" + }, + "require-dev": { + "doctrine/collections": "^1.6.8", + "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", + "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" + }, + "type": "library", + "autoload": { + "files": [ + "src/DeepCopy/deep_copy.php" + ], + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" + }, + "funding": [ + { + "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", + "type": "tidelift" + } + ], + "time": "2025-08-01T08:46:24+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v5.7.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "php": ">=7.4" + }, + "require-dev": { + "ircmaxell/php-yacc": "^0.0.7", + "phpunit/phpunit": "^9.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + }, + "time": "2025-12-06T11:56:16+00:00" + }, + { + "name": "phar-io/manifest", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/phar-io/manifest.git", + "reference": "54750ef60c58e43759730615a392c31c80e23176" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-phar": "*", + "ext-xmlwriter": "*", + "phar-io/version": "^3.0.1", + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", + "support": { + "issues": "https://github.com/phar-io/manifest/issues", + "source": "https://github.com/phar-io/manifest/tree/2.0.4" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" + }, + { + "name": "phar-io/version", + "version": "3.2.1", + "source": { + "type": "git", + "url": "https://github.com/phar-io/version.git", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + }, + { + "name": "Sebastian Heuer", + "email": "sebastian@phpeople.de", + "role": "Developer" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "Developer" + } + ], + "description": "Library for handling version information and constraints", + "support": { + "issues": "https://github.com/phar-io/version/issues", + "source": "https://github.com/phar-io/version/tree/3.2.1" + }, + "time": "2022-02-21T01:04:05+00:00" + }, + { + "name": "phpunit/php-code-coverage", + "version": "12.5.6", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "876099a072646c7745f673d7aeab5382c4439691" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/876099a072646c7745f673d7aeab5382c4439691", + "reference": "876099a072646c7745f673d7aeab5382c4439691", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-libxml": "*", + "ext-xmlwriter": "*", + "nikic/php-parser": "^5.7.0", + "php": ">=8.3", + "phpunit/php-text-template": "^5.0", + "sebastian/complexity": "^5.0", + "sebastian/environment": "^8.0.3", + "sebastian/lines-of-code": "^4.0", + "sebastian/version": "^6.0", + "theseer/tokenizer": "^2.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.1" + }, + "suggest": { + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.5.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/12.5.6" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" + } + ], + "time": "2026-04-15T08:23:17+00:00" + }, + { + "name": "phpunit/php-file-iterator", + "version": "6.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "reference": "3d1cd096ef6bea4bf2762ba586e35dbd317cbfd5", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/6.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator", + "type": "tidelift" + } + ], + "time": "2026-02-02T14:04:18+00:00" + }, + { + "name": "phpunit/php-invoker", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-invoker.git", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "reference": "12b54e689b07a25a9b41e57736dfab6ec9ae5406", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "ext-pcntl": "*", + "phpunit/phpunit": "^12.0" + }, + "suggest": { + "ext-pcntl": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Invoke callables with a timeout", + "homepage": "https://github.com/sebastianbergmann/php-invoker/", + "keywords": [ + "process" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-invoker/issues", + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:58+00:00" + }, + { + "name": "phpunit/php-text-template", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/e1367a453f0eda562eedb4f659e13aa900d66c53", + "reference": "e1367a453f0eda562eedb4f659e13aa900d66c53", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:16+00:00" + }, + { + "name": "phpunit/php-timer", + "version": "8.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "reference": "f258ce36aa457f3aa3339f9ed4c81fc66dc8c2cc", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/8.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:59:38+00:00" + }, + { + "name": "phpunit/phpunit", + "version": "12.5.27", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "f37c01edaf3a0cd820331462506af93f1495696e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f37c01edaf3a0cd820331462506af93f1495696e", + "reference": "f37c01edaf3a0cd820331462506af93f1495696e", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-xml": "*", + "ext-xmlwriter": "*", + "myclabs/deep-copy": "^1.13.4", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.3", + "phpunit/php-code-coverage": "^12.5.6", + "phpunit/php-file-iterator": "^6.0.1", + "phpunit/php-invoker": "^6.0.0", + "phpunit/php-text-template": "^5.0.0", + "phpunit/php-timer": "^8.0.0", + "sebastian/cli-parser": "^4.2.1", + "sebastian/comparator": "^7.1.8", + "sebastian/diff": "^7.0.0", + "sebastian/environment": "^8.1.2", + "sebastian/exporter": "^7.0.3", + "sebastian/global-state": "^8.0.2", + "sebastian/object-enumerator": "^7.0.0", + "sebastian/recursion-context": "^7.0.1", + "sebastian/type": "^6.0.4", + "sebastian/version": "^6.0.0", + "staabm/side-effects-detector": "^1.0.5" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "12.5-dev" + } + }, + "autoload": { + "files": [ + "src/Framework/Assert/Functions.php" + ], + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.5.27" + }, + "funding": [ + { + "url": "https://phpunit.de/sponsoring.html", + "type": "other" + } + ], + "time": "2026-05-25T15:35:34+00:00" + }, + { + "name": "sebastian/cli-parser", + "version": "4.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/cli-parser.git", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/7d05781b13f7dec9043a629a21d086ed74582a15", + "reference": "7d05781b13f7dec9043a629a21d086ed74582a15", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for parsing CLI options", + "homepage": "https://github.com/sebastianbergmann/cli-parser", + "support": { + "issues": "https://github.com/sebastianbergmann/cli-parser/issues", + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/4.2.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser", + "type": "tidelift" + } + ], + "time": "2026-05-17T05:29:34+00:00" + }, + { + "name": "sebastian/comparator", + "version": "7.1.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "7c65c1e79836812819705b473a90c12399542485" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/7c65c1e79836812819705b473a90c12399542485", + "reference": "7c65c1e79836812819705b473a90c12399542485", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/diff": "^7.0", + "sebastian/exporter": "^7.0.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "suggest": { + "ext-bcmath": "For comparing BcMath\\Number objects" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "https://github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/7.1.8" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" + } + ], + "time": "2026-05-21T04:45:25+00:00" + }, + { + "name": "sebastian/complexity", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/complexity.git", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/bad4316aba5303d0221f43f8cee37eb58d384bbb", + "reference": "bad4316aba5303d0221f43f8cee37eb58d384bbb", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for calculating the complexity of PHP code units", + "homepage": "https://github.com/sebastianbergmann/complexity", + "support": { + "issues": "https://github.com/sebastianbergmann/complexity/issues", + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/7ab1ea946c012266ca32390913653d844ecd085f", + "reference": "7ab1ea946c012266ca32390913653d844ecd085f", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0", + "symfony/process": "^7.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:55:46+00:00" + }, + { + "name": "sebastian/environment", + "version": "8.1.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9d32c685773823b1983e256ae4ecd48a10d6e439", + "reference": "9d32c685773823b1983e256ae4ecd48a10d6e439", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.26" + }, + "suggest": { + "ext-posix": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "https://github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/8.1.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" + } + ], + "time": "2026-05-25T13:40:20+00:00" + }, + { + "name": "sebastian/exporter", + "version": "7.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "reference": "c5e21b5de653ce0a769fb36f5cdfcb5e7a32cf23", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": ">=8.3", + "sebastian/recursion-context": "^7.0.1" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "https://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/7.0.3" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/exporter", + "type": "tidelift" + } + ], + "time": "2026-05-20T04:37:17+00:00" + }, + { + "name": "sebastian/global-state", + "version": "8.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/ef1377171613d09edd25b7816f05be8313f9115d", + "reference": "ef1377171613d09edd25b7816f05be8313f9115d", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "ext-dom": "*", + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "8.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/8.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/global-state", + "type": "tidelift" + } + ], + "time": "2025-08-29T11:29:25+00:00" + }, + { + "name": "sebastian/lines-of-code", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/lines-of-code.git", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d543b8ef219dcd8da262cbb958639a96bedba10e", + "reference": "d543b8ef219dcd8da262cbb958639a96bedba10e", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.7.0", + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library for counting the lines of code in PHP source code", + "homepage": "https://github.com/sebastianbergmann/lines-of-code", + "support": { + "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/4.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/lines-of-code", + "type": "tidelift" + } + ], + "time": "2026-05-19T16:22:07+00:00" + }, + { + "name": "sebastian/object-enumerator", + "version": "7.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "reference": "1effe8e9b8e068e9ae228e542d5d11b5d16db894", + "shasum": "" + }, + "require": { + "php": ">=8.3", + "sebastian/object-reflector": "^5.0", + "sebastian/recursion-context": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/7.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:57:48+00:00" + }, + { + "name": "sebastian/object-reflector", + "version": "5.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-reflector.git", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/4bfa827c969c98be1e527abd576533293c634f6a", + "reference": "4bfa827c969c98be1e527abd576533293c634f6a", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Allows reflection of object attributes, including inherited and non-public ones", + "homepage": "https://github.com/sebastianbergmann/object-reflector/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-reflector/issues", + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/5.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T04:58:17+00:00" + }, + { + "name": "sebastian/recursion-context", + "version": "7.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "reference": "0b01998a7d5b1f122911a66bebcb8d46f0c82d8c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "7.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "https://github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/7.0.1" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" + } + ], + "time": "2025-08-13T04:44:59+00:00" + }, + { + "name": "sebastian/type", + "version": "6.0.4", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "82ff822c2edc46724be9f7411d3163021f602773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/82ff822c2edc46724be9f7411d3163021f602773", + "reference": "82ff822c2edc46724be9f7411d3163021f602773", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "require-dev": { + "phpunit/phpunit": "^12.5.25" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "support": { + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/6.0.4" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" + } + ], + "time": "2026-05-20T06:45:45+00:00" + }, + { + "name": "sebastian/version", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/3e6ccf7657d4f0a59200564b08cead899313b53c", + "reference": "3e6ccf7657d4f0a59200564b08cead899313b53c", + "shasum": "" + }, + "require": { + "php": ">=8.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/6.0.0" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2025-02-07T05:00:38+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" + }, + { + "name": "theseer/tokenizer", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/theseer/tokenizer.git", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-tokenizer": "*", + "ext-xmlwriter": "*", + "php": "^8.1" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Arne Blankerts", + "email": "arne@blankerts.de", + "role": "Developer" + } + ], + "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", + "support": { + "issues": "https://github.com/theseer/tokenizer/issues", + "source": "https://github.com/theseer/tokenizer/tree/2.0.1" + }, + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2025-12-08T11:19:18+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": {}, + "prefer-stable": true, + "prefer-lowest": false, + "platform": { + "php": "^8.0" + }, + "platform-dev": {}, + "plugin-api-version": "2.9.0" +} diff --git a/phpunit.dist.xml b/phpunit.dist.xml index 1de961c..9d9f0f9 100644 --- a/phpunit.dist.xml +++ b/phpunit.dist.xml @@ -2,64 +2,22 @@ - - - - - - - - - - - - - - - - - tests/system + + tests - system + src - - system/Commands/Generators/Views - system/Debug/Toolbar/Views - system/Pager/Views - system/ThirdParty - system/Validation/Views - system/bootstrap.php - system/ComposerScripts.php - system/Config/Routes.php - system/Test/bootstrap.php - system/Test/ControllerTester.php - system/Test/FeatureTestCase.php - - - - - - - - - - - - diff --git a/psalm.xml b/psalm.xml index d603331..919dfa0 100644 --- a/psalm.xml +++ b/psalm.xml @@ -13,18 +13,10 @@ findUnusedCode="false" > - - - + + - - - - - - - @@ -32,7 +24,7 @@ - + diff --git a/src/Adapters/AdodbDatabaseAdapter.php b/src/Adapters/AdodbDatabaseAdapter.php index 9f9a635..a10b652 100644 --- a/src/Adapters/AdodbDatabaseAdapter.php +++ b/src/Adapters/AdodbDatabaseAdapter.php @@ -57,19 +57,21 @@ class AdodbDatabaseAdapter implements DatabaseAdapterInterface * Catat satu query ke dalam log. * Dipanggil dari kode aplikasi saat query dieksekusi. * - * @param string $sql Query SQL yang dieksekusi - * @param float $duration Durasi dalam milidetik - * @param array $params Bind parameter (opsional) + * @param string $sql Query SQL yang dieksekusi + * @param float $duration Durasi dalam milidetik + * @param array $params Bind parameter (opsional) + * @param float|null $startTime microtime(true) saat query dimulai (untuk timeline) */ - public static function logQuery(string $sql, float $duration, array $params = []): void + public static function logQuery(string $sql, float $duration, array $params = [], ?float $startTime = null): void { $sql = trim($sql); self::$queries[] = [ - 'sql' => $sql, - 'duration' => round($duration, 4), - 'params' => $params, - 'trace' => self::buildShortTrace(), + 'sql' => $sql, + 'duration' => round($duration, 4), + 'params' => $params, + 'trace' => self::buildShortTrace(), + 'startTime' => $startTime ?? (microtime(true) - $duration / 1000.0), ]; self::$totalTime += $duration; diff --git a/src/Collectors/BaseCollector.php b/src/Collectors/BaseCollector.php index 53cec33..ad7f117 100644 --- a/src/Collectors/BaseCollector.php +++ b/src/Collectors/BaseCollector.php @@ -186,13 +186,13 @@ public function display() } /** - * This makes nicer looking paths for the error output. + * Normalize a file path for display (forward slashes, no trailing sep). * - * @deprecated Use the dedicated `clean_path()` function. + * @deprecated No longer needed โ€” paths are normalized per-collector. */ public function cleanPath(string $file): string { - return clean_path($file); + return str_replace('\\', '/', $file); } /** diff --git a/src/Collectors/Config.php b/src/Collectors/Config.php index 4592f2f..d1d7a6d 100644 --- a/src/Collectors/Config.php +++ b/src/Collectors/Config.php @@ -27,18 +27,20 @@ * Adapted from CodeIgniter 4 to be framework-agnostic. * Returns basic PHP and environment information. */ -class Config extends BaseCollector +class Config { /** * Return toolbar config values as an array. */ - public static function display(): array + public static function display(array $config = []): array { return [ 'phpVersion' => PHP_VERSION, 'phpSAPI' => PHP_SAPI, 'timezone' => date_default_timezone_get(), 'serverOS' => PHP_OS, + 'baseURL' => $config['baseURL'] ?? '', + 'environment' => $config['environment'] ?? '', ]; } } diff --git a/src/Collectors/Database.php b/src/Collectors/Database.php index 8e12135..438608e 100644 --- a/src/Collectors/Database.php +++ b/src/Collectors/Database.php @@ -27,7 +27,11 @@ * Collector for the Database tab of the Debug Toolbar. * * Adapted from CodeIgniter 4 to be framework-agnostic. - * Requires a DatabaseAdapterInterface implementation (e.g., AdodbDatabaseAdapter). + * Use setAdapter() to register a DatabaseAdapterInterface implementation + * (e.g., AdodbDatabaseAdapter) before the toolbar is rendered. + * + * Example: + * Database::setAdapter(new AdodbDatabaseAdapter()); */ class Database extends BaseCollector { @@ -45,13 +49,6 @@ class Database extends BaseCollector */ protected $hasTabContent = true; - /** - * Whether this collector has data for the Vars tab. - * - * @var bool - */ - protected $hasVarData = false; - /** * The name used to reference this collector in the toolbar. * @@ -60,89 +57,40 @@ class Database extends BaseCollector protected $title = 'Database'; /** - * Array of database connections. - * - * @var array - */ - protected $connections; - - /** - * The query instances that have been collected - * through the DBQuery Event. - * - * @var array - */ - protected static $queries = []; - - /** - * Constructor + * Registered database adapter, shared across all instances. */ - public function __construct() - { - $this->getConnections(); - } + private static ?DatabaseAdapterInterface $adapter = null; /** - * The static method used during Events to collect - * data. - * - * @internal - * - * @return void + * Register the database adapter. + * Call this once at bootstrap before the toolbar is rendered. */ - public static function collect(Query $query) + public static function setAdapter(DatabaseAdapterInterface $adapter): void { - $config = config(Toolbar::class); - - // Provide default in case it's not set - $max = $config->maxQueries ?: 100; - - if (count(static::$queries) < $max) { - $queryString = $query->getQuery(); - - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - - if (! is_cli()) { - // when called in the browser, the first two trace arrays - // are from the DB event trigger, which are unneeded - $backtrace = array_slice($backtrace, 2); - } - - static::$queries[] = [ - 'query' => $query, - 'string' => $queryString, - 'duplicate' => in_array($queryString, array_column(static::$queries, 'string'), true), - 'trace' => $backtrace, - ]; - } + self::$adapter = $adapter; } /** * Returns timeline data formatted for the toolbar. - * - * @return array The formatted data or an empty array. */ protected function formatTimelineData(): array { + if (self::$adapter === null) { + return []; + } + $data = []; - foreach ($this->connections as $alias => $connection) { - // Connection Time - $data[] = [ - 'name' => 'Connecting to Database: "' . $alias . '"', - 'component' => 'Database', - 'start' => $connection->getConnectStart(), - 'duration' => $connection->getConnectDuration(), - ]; - } + foreach (self::$adapter->getQueries() as $query) { + $startTime = (float) ($query['startTime'] ?? 0); + $duration = ((float) ($query['duration'] ?? 0)) / 1000; // ms โ†’ seconds - foreach (static::$queries as $query) { $data[] = [ 'name' => 'Query', 'component' => 'Database', - 'start' => $query['query']->getStartTime(true), - 'duration' => $query['query']->getDuration(), - 'query' => $query['query']->debugToolbarDisplay(), + 'start' => $startTime, + 'duration' => $duration, + 'query' => htmlspecialchars($query['sql'] ?? '', ENT_QUOTES, 'UTF-8'), ]; } @@ -150,58 +98,38 @@ protected function formatTimelineData(): array } /** - * Returns the data of this collector to be formatted in the toolbar + * Returns the data of this collector to be formatted in the toolbar. */ public function display(): array { - return ['queries' => array_map(static function (array $query): array { - $isDuplicate = $query['duplicate'] === true; - - $firstNonSystemLine = ''; - - foreach ($query['trace'] as $index => &$line) { - // simplify file and line - if (isset($line['file'])) { - $line['file'] = clean_path($line['file']) . ':' . $line['line']; - unset($line['line']); - } else { - $line['file'] = '[internal function]'; - } - - // find the first trace line that does not originate from `system/` - if ($firstNonSystemLine === '' && ! str_contains($line['file'], 'SYSTEMPATH')) { - $firstNonSystemLine = $line['file']; - } - - // simplify function call - if (isset($line['class'])) { - $line['function'] = $line['class'] . $line['type'] . $line['function']; - unset($line['class'], $line['type']); - } - - if (strrpos($line['function'], '{closure}') === false) { - $line['function'] .= '()'; - } + if (self::$adapter === null) { + return ['queries' => []]; + } - $line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function']; + $rawQueries = self::$adapter->getQueries(); + $sqlCounts = array_count_values(array_column($rawQueries, 'sql')); - // add index numbering padded with nonbreaking space - $indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT); - $indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded); + $queries = []; + $idx = 0; - $line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4); - } + foreach ($rawQueries as $query) { + $sql = $query['sql'] ?? ''; + $isDuplicate = ($sqlCounts[$sql] ?? 1) > 1; - return [ + $queries[] = [ 'hover' => $isDuplicate ? 'This query was called more than once.' : '', 'class' => $isDuplicate ? 'duplicate' : '', - 'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms', - 'sql' => $query['query']->debugToolbarDisplay(), - 'trace' => $query['trace'], - 'trace-file' => $firstNonSystemLine, - 'qid' => md5($query['query'] . Time::now()->format('0.u00 U')), + 'duration' => number_format((float) ($query['duration'] ?? 0), 2) . ' ms', + 'sql' => htmlspecialchars($sql, ENT_QUOTES, 'UTF-8'), + 'trace' => $query['trace'] ?? '', + 'trace-file' => $query['trace'] ?? '', + 'qid' => md5($sql . $idx), ]; - }, static::$queries)]; + + $idx++; + } + + return ['queries' => $queries]; } /** @@ -209,30 +137,26 @@ public function display(): array */ public function getBadgeValue(): int { - return count(static::$queries); + return self::$adapter !== null ? self::$adapter->getQueryCount() : 0; } /** * Information to be displayed next to the title. - * - * @return string The number of queries (in parentheses) or an empty string. */ public function getTitleDetails(): string { - $this->getConnections(); + if (self::$adapter === null) { + return ''; + } - $queryCount = count(static::$queries); - $uniqueCount = count(array_filter(static::$queries, static fn ($query): bool => $query['duplicate'] === false)); - $connectionCount = count($this->connections); + $total = self::$adapter->getQueryCount(); + $duplicates = count(self::$adapter->getDuplicates()); return sprintf( - '(%d total Quer%s, %d %s unique across %d Connection%s)', - $queryCount, - $queryCount > 1 ? 'ies' : 'y', - $uniqueCount, - $uniqueCount > 1 ? 'of them' : '', - $connectionCount, - $connectionCount > 1 ? 's' : '', + '(%d total Quer%s, %d duplicate)', + $total, + $total === 1 ? 'y' : 'ies', + $duplicates, ); } @@ -241,7 +165,7 @@ public function getTitleDetails(): string */ public function isEmpty(): bool { - return static::$queries === []; + return self::$adapter === null || self::$adapter->getQueryCount() === 0; } /** @@ -255,19 +179,10 @@ public function icon(): string } /** - * Gets the connections from the database config - */ - private function getConnections(): void - { - $this->connections = \Config\Database::getConnections(); - } - - /** - * Reset collector state for worker mode. - * Clears collected queries between requests. + * Reset collector state. */ - public function reset(): void + public static function reset(): void { - static::$queries = []; + self::$adapter = null; } } diff --git a/src/Collectors/Events.php b/src/Collectors/Events.php index c683d2b..aa55a9c 100644 --- a/src/Collectors/Events.php +++ b/src/Collectors/Events.php @@ -25,6 +25,11 @@ * Events collector * * Adapted from CodeIgniter 4 to be framework-agnostic. + * + * Usage: + * $start = microtime(true); + * // ... event handler runs ... + * Events::trigger('my_event', $start, microtime(true)); */ class Events extends BaseCollector { @@ -60,6 +65,26 @@ class Events extends BaseCollector */ protected $title = 'Events'; + /** + * @var list + */ + private static array $logs = []; + + /** + * Log a triggered event. + * + * @param float $start microtime(true) before the event handler ran + * @param float $end microtime(true) after the event handler returned + */ + public static function trigger(string $event, float $start, float $end): void + { + self::$logs[] = [ + 'event' => $event, + 'start' => $start, + 'end' => $end, + ]; + } + /** * Child classes should implement this to return the timeline data * formatted for correct usage. @@ -68,9 +93,7 @@ protected function formatTimelineData(): array { $data = []; - $rows = \CodeIgniter\Events\Events::getPerformanceLogs(); - - foreach ($rows as $info) { + foreach (self::$logs as $info) { $data[] = [ 'name' => 'Event: ' . $info['event'], 'component' => 'Events', @@ -87,15 +110,13 @@ protected function formatTimelineData(): array */ public function display(): array { - $data = [ - 'events' => [], - ]; + $events = []; - foreach (\CodeIgniter\Events\Events::getPerformanceLogs() as $row) { + foreach (self::$logs as $row) { $key = $row['event']; - if (! array_key_exists($key, $data['events'])) { - $data['events'][$key] = [ + if (! array_key_exists($key, $events)) { + $events[$key] = [ 'event' => $key, 'duration' => ($row['end'] - $row['start']) * 1000, 'count' => 1, @@ -104,15 +125,15 @@ public function display(): array continue; } - $data['events'][$key]['duration'] += ($row['end'] - $row['start']) * 1000; - $data['events'][$key]['count']++; + $events[$key]['duration'] += ($row['end'] - $row['start']) * 1000; + $events[$key]['count']++; } - foreach ($data['events'] as &$row) { + foreach ($events as &$row) { $row['duration'] = number_format($row['duration'], 2); } - return $data; + return ['events' => array_values($events)]; } /** @@ -120,7 +141,15 @@ public function display(): array */ public function getBadgeValue(): int { - return count(\CodeIgniter\Events\Events::getPerformanceLogs()); + return count(self::$logs); + } + + /** + * Does this collector have any data collected? + */ + public function isEmpty(): bool + { + return self::$logs === []; } /** @@ -132,4 +161,12 @@ public function icon(): string { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEASURBVEhL7ZXNDcIwDIVTsRBH1uDQDdquUA6IM1xgCA6MwJUN2hk6AQzAz0vl0ETUxC5VT3zSU5w81/mRMGZysixbFEVR0jSKNt8geQU9aRpFmp/keX6AbjZ5oB74vsaN5lSzA4tLSjpBFxsjeSuRy4d2mDdQTWU7YLbXTNN05mKyovj5KL6B7q3hoy3KwdZxBlT+Ipz+jPHrBqOIynZgcZonoukb/0ckiTHqNvDXtXEAaygRbaB9FvUTjRUHsIYS0QaSp+Dw6wT4hiTmYHOcYZsdLQ2CbXa4ftuuYR4x9vYZgdb4vsFYUdmABMYeukK9/SUme3KMFQ77+Yfzh8eYF8+orDuDWU5LAAAAAElFTkSuQmCC'; } + + /** + * Reset all logged events. + */ + public static function reset(): void + { + self::$logs = []; + } } diff --git a/src/Collectors/Files.php b/src/Collectors/Files.php index 89e3df3..c2f2321 100644 --- a/src/Collectors/Files.php +++ b/src/Collectors/Files.php @@ -25,6 +25,8 @@ * Files collector * * Adapted from CodeIgniter 4 to be framework-agnostic. + * Lists all PHP files loaded during this request, separated into + * "vendor / core" files and "user application" files. */ class Files extends BaseCollector { @@ -70,9 +72,9 @@ public function display(): array $userFiles = []; foreach ($rawFiles as $file) { - $path = clean_path($file); + $path = $this->normalizePath($file); - if (str_contains($path, 'SYSTEMPATH')) { + if ($this->isCoreFile($path)) { $coreFiles[] = [ 'path' => $path, 'name' => basename($file), @@ -111,4 +113,21 @@ public function icon(): string { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGBSURBVEhL7ZQ9S8NQGIVTBQUncfMfCO4uLgoKbuKQOWg+OkXERRE1IAXrIHbVDrqIDuLiJgj+gro7S3dnpfq88b1FMTE3VZx64HBzzvvZWxKnj15QCcPwCD5HUfSWR+JtzgmtsUcQBEva5IIm9SwSu+95CAWbUuy67qBa32ByZEDpIaZYZSZMjjQuPcQUq8yEyYEb8FSerYeQVGbAFzJkX1PyQWLhgCz0BxTCekC1Wp0hsa6yokzhed4oje6Iz6rlJEkyIKfUEFtITVtQdAibn5rMyaYsMS+a5wTv8qeXMhcU16QZbKgl3hbs+L4/pnpdc87MElZgq10p5DxGdq8I7xrvUWUKvG3NbSK7ubngYzdJwSsF7TiOh9VOgfcEz1UayNe3JUPM1RWC5GXYgTfc75B4NBmXJnAtTfpABX0iPvEd9ezALwkplCFXcr9styiNOKc1RRZpaPM9tcqBwlWzGY1qPL9wjqRBgF5BH6j8HWh2S7MHlX8PrmbK+k/8PzjOOzx1D3i1pKTTAAAAAElFTkSuQmCC'; } + + /** + * Convert backslashes to forward slashes for uniform display. + */ + private function normalizePath(string $file): string + { + return str_replace('\\', '/', $file); + } + + /** + * Heuristic: files inside a vendor/ directory are "core/library" files; + * everything else is considered a user application file. + */ + private function isCoreFile(string $normalizedPath): bool + { + return str_contains($normalizedPath, '/vendor/'); + } } diff --git a/src/Collectors/History.php b/src/Collectors/History.php index 83a5713..b7fc034 100644 --- a/src/Collectors/History.php +++ b/src/Collectors/History.php @@ -65,6 +65,21 @@ class History extends BaseCollector */ protected $files = []; + /** + * Path to directory containing debugbar JSON history files. + * + * @var string + */ + private string $historyPath; + + public function __construct(array $config = []) + { + $this->historyPath = rtrim( + $config['historyPath'] ?? sys_get_temp_dir() . '/wizdam-debugbar/', + '/\\', + ) . DIRECTORY_SEPARATOR; + } + /** * Specify time limit & file count for debug history. * @@ -75,7 +90,7 @@ class History extends BaseCollector */ public function setFiles(string $current, int $limit = 20) { - $filenames = glob(WRITEPATH . 'debugbar/debugbar_*.json'); + $filenames = glob($this->historyPath . 'debugbar_*.json') ?: []; $files = []; $counter = 0; @@ -99,15 +114,16 @@ public function setFiles(string $current, int $limit = 20) $time = sprintf('%.6F', $time[1] ?? 0); // Debugbar files shown in History Collector + $dt = date_create_from_format('U.u', $time); $files[] = [ 'time' => $time, - 'datetime' => DateTime::createFromFormat('U.u', $time)->format('Y-m-d H:i:s.u'), + 'datetime' => $dt !== false ? $dt->format('Y-m-d H:i:s.u') : '', 'active' => $time === $current, - 'status' => $contents->vars->response->statusCode, - 'method' => $contents->method, - 'url' => $contents->url, - 'isAJAX' => $contents->isAJAX ? 'Yes' : 'No', - 'contentType' => $contents->vars->response->contentType, + 'status' => $contents->vars->response->statusCode ?? 0, + 'method' => $contents->method ?? '', + 'url' => $contents->url ?? '', + 'isAJAX' => !empty($contents->isAJAX) ? 'Yes' : 'No', + 'contentType' => $contents->vars->response->contentType ?? '', ]; } } @@ -146,6 +162,6 @@ public function isEmpty(): bool */ public function icon(): string { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJySURBVEhL3ZU7aJNhGIVTpV6i4qCIgkIHxcXLErS4FBwUFNwiCKGhuTYJGaIgnRoo4qRu6iCiiIuIXXTTIkIpuqoFwaGgonUQlC5KafU5ycmNP0lTdPLA4fu+8573/a4/f6hXpFKpwUwmc9fDfweKbk+n07fgEv33TLSbtt/hvwNFT1PsG/zdTE0Gp+GFfD6/2fbVIxqNrqPIRbjg4t/hY8aztcngfDabHXbKyiiXy2vcrcPH8oDCry2FKDrA+Ar6L01E/ypyXzXaARjDGGcoeNxSDZXE0dHRA5VRE5LJ5CFy5jzJuOX2wHRHRnjbklZ6isQ3tIctBaAd4vlK3jLtkOVWqABBXd47jGHLmjTmSScttQV5J+SjfcUweFQEbsjAas5aqoCLXutJl7vtQsAzpRowYqkBinyCC8Vicb2lOih8zoldd0F8RD7qTFiqAnGrAy8stUAvi/hbqDM+YzkAFrLPdR5ZqoLXsd+Bh5YCIH7JniVdquUWxOPxDfboHhrI5XJ7HHhiqQXox+APe/Qk64+gGYVCYZs8cMpSFQj9JOoFzVqqo7k4HIvFYpscCoAjOmLffUsNUGRaQUwDlmofUa34ecsdgXdcXo4wbakBgiUFafXJV8A4DJ/2UrxUKm3E95H8RbjLcgOJRGILhnmCP+FBy5XvwN2uIPcy1AJvWgqC4xm2aU4Xb3lF4I+Tpyf8hRe5w3J7YLymSeA8Z3nSclv4WLRyFdfOjzrUFX0klJUEtZtntCNc+F69cz/FiDzEPtjzmcUMOr83kDQEX6pAJxJfpL3OX22n01YN7SZCoQnaSdoZ+Jz+PZihH3wt/xlCoT9M6nEtmRSPCQAAAABJRU5ErkJggg=='; + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJySURBVEhL3ZU7aJNhGIVTpV6i4qCIgkIHxcXLErS4FBwUFNwiCKGhuTYJGaIgnRoo4qRu6iCiiIuIXXTTIkIpuqoFwaGgonUQlC5KafU5ycmNP0lTdPLA4fs+c973va4/f6hXpFKpwUwmc9fDfweKbk+n07fgEv33TLSbtt/hvwNFT1PsG/zdTE0Gp+GFfD6/2fbVIxqNrqPIRbjg4t/hY8aztcngfDabHTrKyiiXy2vcrcPH8oDCry2FKDrA+Ar6L01E/ypyXzXaARjDGGcoeNxSDZXE0dHRA5VRE5LJ5CFy5jzJuOX2wHRHRnjbklZ6isQ3tIctBaAd4vlK3jLtkOVWqABBXd47jGHLmjTmSScttQV5J+SjfcUweFQEbsjAas5aqoCLXutJl7vtQsAzpRowYqkBinyCC8Vicb2lOih8zoldd0F8RD7qTFiqAnGrAy8stUAvi/hbqDM+YzkAFrLPdR5ZqoLXsd+Bh5YCIH7JniVdquUWxOPxDfboHhrI5XJ7HHhiqQXox+APe/Qk64+gGYVCYZs8cMpSFQj9JOoFzVqqo7k4HIvFYpscCoAjOmLffUsNUGRaQUwDlmofUa34ecsdgXdcXo4wbakBgiUFafXJV8A4DJ/2UrxUKm3E95H8RbjLcgOJRGILhnmCP+FBy5XvwN2uIPcy1AJvWgqC4xm2aU4Xb3lF4I+Tpyf8hRe5w3J7YLymSeA8Z3nSclv4WLRyFdfOjzrUFX0klJUEtZtntCNc+F69cz/FiDzEPtjzmcUMOr83kDQEX6pAJxJfpL3OX22n01YN7SZCoQnaSdoZ+Jz+PZihH3wt/xlCoT9M6nEtmRSPCQAAAABJRU5ErkJggg=='; } } diff --git a/src/Collectors/Logs.php b/src/Collectors/Logs.php index 4a89de7..f457add 100644 --- a/src/Collectors/Logs.php +++ b/src/Collectors/Logs.php @@ -25,6 +25,10 @@ * Logs collector * * Adapted from CodeIgniter 4 to be framework-agnostic. + * + * Usage: + * Logs::addLog('error', 'Something went wrong'); + * Logs::addLog('info', 'User logged in'); */ class Logs extends BaseCollector { @@ -53,11 +57,23 @@ class Logs extends BaseCollector protected $title = 'Logs'; /** - * Our collected data. - * * @var list */ - protected $data = []; + private static array $logCache = []; + + /** + * Add a log entry. + * + * @param string $level PSR-3 log level: emergency, alert, critical, error, warning, notice, info, debug + * @param string $msg Log message + */ + public static function addLog(string $level, string $msg): void + { + self::$logCache[] = [ + 'level' => $level, + 'msg' => $msg, + ]; + } /** * Returns the data of this collector to be formatted in the toolbar. @@ -66,9 +82,7 @@ class Logs extends BaseCollector */ public function display(): array { - return [ - 'logs' => $this->collectLogs(), - ]; + return ['logs' => self::$logCache]; } /** @@ -76,9 +90,7 @@ public function display(): array */ public function isEmpty(): bool { - $this->collectLogs(); - - return $this->data === []; + return self::$logCache === []; } /** @@ -92,20 +104,10 @@ public function icon(): string } /** - * Ensures the data has been collected. - * - * @return list + * Reset all log entries. */ - protected function collectLogs() + public static function reset(): void { - if ($this->data !== []) { - return $this->data; - } - - $cache = service('logger')->logCache; - - $this->data = $cache ?? []; - - return $this->data; + self::$logCache = []; } } diff --git a/src/Collectors/Routes.php b/src/Collectors/Routes.php index 0e23333..3184324 100644 --- a/src/Collectors/Routes.php +++ b/src/Collectors/Routes.php @@ -27,7 +27,9 @@ * Routes collector * * Adapted from CodeIgniter 4 to be framework-agnostic. - * Requires a RouterInterface implementation (e.g., WizdamRouterAdapter). + * + * Usage: + * Routes::setRouter(new WizdamRouterAdapter()); */ class Routes extends BaseCollector { @@ -56,7 +58,21 @@ class Routes extends BaseCollector protected $title = 'Routes'; /** - * Returns the data of this collector to be formatted in the toolbar + * Registered router adapter, shared across all instances. + */ + private static ?RouterInterface $router = null; + + /** + * Register the router adapter. + * Call this once at bootstrap before the toolbar is rendered. + */ + public static function setRouter(RouterInterface $router): void + { + self::$router = $router; + } + + /** + * Returns the data of this collector to be formatted in the toolbar. * * @return array{ * matchedRoute: list + * params: list * }>, - * routes: list + * routes: list * } - * - * @throws ReflectionException */ public function display(): array { - $rawRoutes = service('routes', true); - $router = service('router', null, null, true); - - // Get our parameters - // Closure routes - if (is_callable($router->controllerName())) { - $method = new ReflectionFunction($router->controllerName()); - } else { - try { - $method = new ReflectionMethod($router->controllerName(), $router->methodName()); - } catch (ReflectionException) { - try { - // If we're here, the method doesn't exist - // and is likely calculated in _remap. - $method = new ReflectionMethod($router->controllerName(), '_remap'); - } catch (ReflectionException) { - // If we're here, page cache is returned. The router is not executed. - return [ - 'matchedRoute' => [], - 'routes' => [], - ]; - } - } + if (self::$router === null) { + return [ + 'matchedRoute' => [], + 'routes' => [], + ]; } - $rawParams = $method->getParameters(); - - $params = []; + $rawParams = self::$router->getParams(); + $params = []; - foreach ($rawParams as $key => $param) { + foreach ($rawParams as $name => $value) { $params[] = [ - 'name' => '$' . $param->getName() . ' = ', - 'value' => $router->params()[$key] ?? - ' | default: ' - . var_export( - $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, - true, - ), + 'name' => (string) $name, + 'value' => is_scalar($value) ? (string) $value : print_r($value, true), ]; } $matchedRoute = [ [ - 'directory' => $router->directory(), - 'controller' => $router->controllerName(), - 'method' => $router->methodName(), - 'paramCount' => count($router->params()), + 'directory' => '', + 'controller' => self::$router->getController(), + 'method' => self::$router->getMethod(), + 'paramCount' => count($rawParams), 'truePCount' => count($params), 'params' => $params, ], ]; - // Defined Routes - $routes = []; - - $definedRouteCollector = new DefinedRouteCollector($rawRoutes); - - foreach ($definedRouteCollector->collect() as $route) { - // filter for strings, as callbacks aren't displayable - if ($route['handler'] !== '(Closure)') { - $routes[] = [ - 'method' => strtoupper($route['method']), - 'route' => $route['route'], - 'handler' => $route['handler'], - ]; - } - } - return [ 'matchedRoute' => $matchedRoute, - 'routes' => $routes, + 'routes' => [], ]; } /** - * Returns a count of all the routes in the system. + * Returns the number of matched route entries as the badge value. */ public function getBadgeValue(): int { - $rawRoutes = service('routes', true); + return self::$router !== null ? 1 : 0; + } - return count($rawRoutes->getRoutes()); + /** + * Does this collector have any data collected? + */ + public function isEmpty(): bool + { + return self::$router === null; } /** @@ -174,4 +147,12 @@ public function icon(): string { return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFDSURBVEhL7ZRNSsNQFIUjVXSiOFEcuQIHDpzpxC0IGYeE/BEInbWlCHEDLsSiuANdhKDjgm6ggtSJ+l25ldrmmTwIgtgDh/t37r1J+16cX0dRFMtpmu5pWAkrvYjjOB7AETzStBFW+inxu3KUJMmhludQpoflS1zXban4LYqiO224h6VLTHr8Z+z8EpIHFF9gG78nDVmW7UgTHKjsCyY98QP+pcq+g8Ku2s8G8X3f3/I8b038WZTp+bO38zxfFd+I6YY6sNUvFlSDk9CRhiAI1jX1I9Cfw7GG1UB8LAuwbU0ZwQnbRDeEN5qqBxZMLtE1ti9LtbREnMIuOXnyIf5rGIb7Wq8HmlZgwYBH7ORTcKH5E4mpjeGt9fBZcHE2GCQ3Vt7oTNPNg+FXLHnSsHkw/FR+Gg2bB8Ptzrst/v6C/wrH+QB+duli6MYJdQAAAABJRU5ErkJggg=='; } + + /** + * Reset the registered router. + */ + public static function reset(): void + { + self::$router = null; + } } diff --git a/src/Collectors/Timers.php b/src/Collectors/Timers.php index 820e526..fdb8db2 100644 --- a/src/Collectors/Timers.php +++ b/src/Collectors/Timers.php @@ -25,6 +25,11 @@ * Timers collector * * Adapted from CodeIgniter 4 to be framework-agnostic. + * + * Usage: + * Timers::start('my_block'); + * // ... code ... + * Timers::stop('my_block'); */ class Timers extends BaseCollector { @@ -52,6 +57,39 @@ class Timers extends BaseCollector */ protected $title = 'Timers'; + /** + * @var array + */ + private static array $timers = []; + + /** + * Start a named timer. + */ + public static function start(string $name): void + { + self::$timers[$name] = ['start' => microtime(true), 'end' => null]; + } + + /** + * Stop a named timer. + */ + public static function stop(string $name): void + { + if (isset(self::$timers[$name])) { + self::$timers[$name]['end'] = microtime(true); + } + } + + /** + * Returns all recorded timers (read-only). + * + * @return array + */ + public static function getTimers(): array + { + return self::$timers; + } + /** * Child classes should implement this to return the timeline data * formatted for correct usage. @@ -60,22 +98,29 @@ protected function formatTimelineData(): array { $data = []; - $benchmark = service('timer', true); - $rows = $benchmark->getTimers(6); - - foreach ($rows as $name => $info) { + foreach (self::$timers as $name => $timer) { if ($name === 'total_execution') { continue; } + $end = $timer['end'] ?? microtime(true); + $data[] = [ 'name' => ucwords(str_replace('_', ' ', $name)), 'component' => 'Timer', - 'start' => $info['start'], - 'duration' => $info['end'] - $info['start'], + 'start' => $timer['start'], + 'duration' => $end - $timer['start'], ]; } return $data; } + + /** + * Reset all timers (e.g. between requests in worker mode). + */ + public static function reset(): void + { + self::$timers = []; + } } diff --git a/src/Collectors/Views.php b/src/Collectors/Views.php index cefd99c..2b8c293 100644 --- a/src/Collectors/Views.php +++ b/src/Collectors/Views.php @@ -25,7 +25,11 @@ * Views collector * * Adapted from CodeIgniter 4 to be framework-agnostic. - * Works with any template engine via manual logging. + * + * Usage: + * $start = microtime(true); + * // ... render template ... + * Views::logView('article/view.tpl', $start, microtime(true), $templateData); */ class Views extends BaseCollector { @@ -70,22 +74,26 @@ class Views extends BaseCollector protected $title = 'Views'; /** - * Instance of the shared Renderer service - * - * @var RendererInterface|null + * @var list */ - protected $viewer; + private static array $renderedViews = []; /** - * Views counter + * Log a rendered view. * - * @var array + * @param string $view Template name / path + * @param float $start microtime(true) before rendering + * @param float $end microtime(true) after rendering + * @param array $data Variables passed to the template (optional) */ - protected $views = []; - - private function initViewer(): void + public static function logView(string $view, float $start, float $end, array $data = []): void { - $this->viewer ??= service('renderer'); + self::$renderedViews[] = [ + 'view' => $view, + 'start' => $start, + 'end' => $end, + 'data' => $data, + ]; } /** @@ -94,13 +102,9 @@ private function initViewer(): void */ protected function formatTimelineData(): array { - $this->initViewer(); - $data = []; - $rows = $this->viewer->getPerformanceData(); - - foreach ($rows as $info) { + foreach (self::$renderedViews as $info) { $data[] = [ 'name' => 'View: ' . $info['view'], 'component' => 'Views', @@ -114,37 +118,34 @@ protected function formatTimelineData(): array /** * Gets a collection of data that should be shown in the 'Vars' tab. - * The format is an array of sections, each with their own array - * of key/value pairs: - * - * $data = [ - * 'section 1' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * 'section 2' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * ]; */ public function getVarData(): array { - $this->initViewer(); + $merged = []; - return [ - 'View Data' => $this->viewer->getData(), - ]; + foreach (self::$renderedViews as $info) { + foreach ($info['data'] as $key => $value) { + $merged[(string) $key] = $value; + } + } + + return ['View Data' => $merged]; } /** - * Returns a count of all views. + * Returns a count of all views rendered. */ public function getBadgeValue(): int { - $this->initViewer(); + return count(self::$renderedViews); + } - return count($this->viewer->getPerformanceData()); + /** + * Does this collector have any data collected? + */ + public function isEmpty(): bool + { + return self::$renderedViews === []; } /** @@ -154,6 +155,14 @@ public function getBadgeValue(): int */ public function icon(): string { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADeSURBVEhL7ZSxDcIwEEWNYA0YgGmgyAaJLTcUaaBzQQEVjMEabBQxAdw53zTHiThEovGTfnE/9rsoRUxhKLOmaa6Uh7X2+UvguLCzVxN1XW9x4EYHzik033Hp3X0LO+DaQG8MDQcuq6qao4qkHuMgQggLvkPLjqh00ZgFDBacMJYFkuwFlH1mshdkZ5JPJERA9JpI6xNCBESvibQ+IURA9JpI6xNCBESvibQ+IURA9DTsuHTOrVFFxixgB/eUFlU8uKJ0eDBFOu/9EvoeKnlJS2/08Tc8NOwQ8sIfMeYFjqKDjdU2sp4AAAAASUVORK5CYII='; + return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADeSURBVEhL7ZSxDcIwEEWNYA0YgGmgyAaJLTcUaaBzQQEVjMEabBQxAdw53zTHiThEovGTfnE/9rsoRUxhKLOmaa6Uh7X2+UvguLCzVxN1XW9x4EYHzik033Hp3X0LO+DaQG8MDQcuq6qao4qkHuMgQggLvkPLjqh00ZgFDBacMJYFkuwFlH1mshdkZ5JPJURA9JpI6xNCBESvibQ+IURA9JpI6xNCBESvibQ+IURA9DTsuHTOrVFFxixgB/eUFlU8uKJ0eDBFOu/9EvoeKnlJS2/08Tc8NOwQ8sIfMeYFjqKDjdU2sp4AAAAASUVORK5CYII='; + } + + /** + * Reset all logged views. + */ + public static function reset(): void + { + self::$renderedViews = []; } } diff --git a/src/Middleware/DebugToolbarMiddleware.php b/src/Middleware/DebugToolbarMiddleware.php index d7f1b8c..e892428 100644 --- a/src/Middleware/DebugToolbarMiddleware.php +++ b/src/Middleware/DebugToolbarMiddleware.php @@ -108,19 +108,13 @@ public function process(array $request, callable $handler): string // --------------------------------------------------------------- /** - * Inject toolbar HTML ke dalam response sebelum tag . + * Inject toolbar script loader ke dalam response. + * Menggunakan DebugToolbar::prepare() yang memodifikasi response by reference. */ private function inject(string $response): string { - $toolbarHtml = $this->debugBar->render(); - - // Inject tepat sebelum agar tidak mengganggu layout - if (stripos($response, '') !== false) { - return str_ireplace('', $toolbarHtml . '', $response); - } - - // Fallback: tambahkan di akhir jika tidak ada - return $response . $toolbarHtml; + $this->debugBar->prepare($response); + return $response; } /** diff --git a/src_DEPRICATED/Adapters/AdodbDatabaseAdapter.php b/src_DEPRICATED/Adapters/AdodbDatabaseAdapter.php deleted file mode 100644 index 46d1a6e..0000000 --- a/src_DEPRICATED/Adapters/AdodbDatabaseAdapter.php +++ /dev/null @@ -1,163 +0,0 @@ -Execute($sql, $params); - * $duration = (microtime(true) - $start) * 1000; - * AdodbDatabaseAdapter::logQuery($sql, $duration, $params ?? []); - * - * Atau, untuk integrasi otomatis, extend ADOConnection: - * - * class WizdamAdodbConnection extends ADOConnection { - * public function Execute($sql, $inputarr = false) { - * $start = microtime(true); - * $result = parent::Execute($sql, $inputarr); - * $ms = (microtime(true) - $start) * 1000; - * AdodbDatabaseAdapter::logQuery( - * is_string($sql) ? $sql : $sql->sql, - * $ms, - * is_array($inputarr) ? $inputarr : [] - * ); - * return $result; - * } - * } - * --- - */ -class AdodbDatabaseAdapter implements DatabaseAdapterInterface -{ - /** @var array */ - private static array $queries = []; - - private static float $totalTime = 0.0; - - /** - * Catat satu query ke dalam log. - * Dipanggil dari kode aplikasi saat query dieksekusi. - * - * @param string $sql Query SQL yang dieksekusi - * @param float $duration Durasi dalam milidetik - * @param array $params Bind parameter (opsional) - */ - public static function logQuery(string $sql, float $duration, array $params = []): void - { - $sql = trim($sql); - - self::$queries[] = [ - 'sql' => $sql, - 'duration' => round($duration, 4), - 'params' => $params, - 'trace' => self::buildShortTrace(), - ]; - - self::$totalTime += $duration; - } - - /** - * Reset log (berguna untuk testing atau profiling per-segmen). - */ - public static function reset(): void - { - self::$queries = []; - self::$totalTime = 0.0; - } - - // --------------------------------------------------------------- - // Implementasi DatabaseAdapterInterface - // --------------------------------------------------------------- - - public function getQueries(): array - { - return self::$queries; - } - - public function getTotalTime(): float - { - return round(self::$totalTime, 4); - } - - public function getQueryCount(): int - { - return count(self::$queries); - } - - public function getDuplicates(): array - { - $counts = []; - - foreach (self::$queries as $entry) { - // Normalisasi whitespace agar query yang sama terdeteksi meski - // diformat berbeda - $normalized = preg_replace('/\s+/', ' ', strtolower($entry['sql'])); - $counts[$normalized] = ($counts[$normalized] ?? 0) + 1; - } - - $duplicates = []; - foreach ($counts as $sql => $count) { - if ($count > 1) { - $duplicates[] = [ - 'sql' => $sql, - 'count' => $count, - ]; - } - } - - return $duplicates; - } - - // --------------------------------------------------------------- - // Helper privat - // --------------------------------------------------------------- - - /** - * Buat ringkasan stack trace yang relevan (tanpa noise framework). - * Hanya menampilkan 3 frame pertama di luar adapter ini. - */ - private static function buildShortTrace(): string - { - $frames = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); - $lines = []; - - foreach ($frames as $frame) { - // Lewati frame dari adapter ini sendiri - if (isset($frame['class']) && $frame['class'] === self::class) { - continue; - } - - $file = isset($frame['file']) ? basename($frame['file']) : '[internal]'; - $line = $frame['line'] ?? 0; - $function = $frame['function'] ?? ''; - $class = isset($frame['class']) ? $frame['class'] . '::' : ''; - - $lines[] = "{$file}:{$line} {$class}{$function}()"; - - if (count($lines) >= 3) { - break; - } - } - - return implode(' โ†’ ', $lines); - } -} \ No newline at end of file diff --git a/src_DEPRICATED/Adapters/OjsRouterAdapter.php b/src_DEPRICATED/Adapters/OjsRouterAdapter.php deleted file mode 100644 index f61b758..0000000 --- a/src_DEPRICATED/Adapters/OjsRouterAdapter.php +++ /dev/null @@ -1,133 +0,0 @@ -getController(); - $op = $this->getMethod(); - - return '/' . $page . '/' . $op; - } - - public function getController(): string - { - return $this->sanitize($_REQUEST['page'] ?? 'index'); - } - - public function getMethod(): string - { - return $this->sanitize($_REQUEST['op'] ?? 'index'); - } - - public function getParams(): array - { - $params = []; - - // Gabungkan GET dan POST, hilangkan parameter routing internal - $all = array_merge($_GET, $_POST); - - foreach ($all as $key => $value) { - if (in_array($key, self::ROUTING_PARAMS, true)) { - continue; - } - $params[$this->sanitize((string) $key)] = $this->sanitizeValue($value); - } - - return $params; - } - - public function getHttpMethod(): string - { - return strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'); - } - - // --------------------------------------------------------------- - // Helper - // --------------------------------------------------------------- - - /** - * OJS path params: ?path[]=volume&path[]=issue - * Dikembalikan sebagai string: 'volume/issue' - */ - public function getPathArray(): string - { - if (!isset($_REQUEST['path'])) { - return ''; - } - - $path = $_REQUEST['path']; - - if (is_array($path)) { - return implode('/', array_map([$this, 'sanitize'], $path)); - } - - return $this->sanitize((string) $path); - } - - /** - * Nama jurnal/press yang sedang aktif (konteks OJS multi-journal). - */ - public function getJournalContext(): string - { - return $this->sanitize( - $_REQUEST['journal'] ?? $_REQUEST['press'] ?? $_SERVER['HTTP_HOST'] ?? 'unknown' - ); - } - - /** - * URL lengkap request saat ini. - */ - public function getRequestUri(): string - { - return $_SERVER['REQUEST_URI'] ?? '/'; - } - - private function sanitize(string $value): string - { - return htmlspecialchars(strip_tags($value), ENT_QUOTES, 'UTF-8'); - } - - private function sanitizeValue(mixed $value): mixed - { - if (is_string($value)) { - return $this->sanitize($value); - } - - if (is_array($value)) { - return array_map(fn($v) => is_string($v) ? $this->sanitize($v) : $v, $value); - } - - return $value; - } -} \ No newline at end of file diff --git a/src_DEPRICATED/Collectors/BaseCollector.php b/src_DEPRICATED/Collectors/BaseCollector.php deleted file mode 100644 index 5d9cae6..0000000 --- a/src_DEPRICATED/Collectors/BaseCollector.php +++ /dev/null @@ -1,238 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -/** - * Base Toolbar collector - */ -class BaseCollector -{ - /** - * Whether this collector has data that can - * be displayed in the Timeline. - * - * @var bool - */ - protected $hasTimeline = false; - - /** - * Whether this collector needs to display - * content in a tab or not. - * - * @var bool - */ - protected $hasTabContent = false; - - /** - * Whether this collector needs to display - * a label or not. - * - * @var bool - */ - protected $hasLabel = false; - - /** - * Whether this collector has data that - * should be shown in the Vars tab. - * - * @var bool - */ - protected $hasVarData = false; - - /** - * The 'title' of this Collector. - * Used to name things in the toolbar HTML. - * - * @var string - */ - protected $title = ''; - - /** - * Gets the Collector's title. - */ - public function getTitle(bool $safe = false): string - { - if ($safe) { - return str_replace(' ', '-', strtolower($this->title)); - } - - return $this->title; - } - - /** - * Returns any information that should be shown next to the title. - */ - public function getTitleDetails(): string - { - return ''; - } - - /** - * Does this collector need it's own tab? - */ - public function hasTabContent(): bool - { - return (bool) $this->hasTabContent; - } - - /** - * Does this collector have a label? - */ - public function hasLabel(): bool - { - return (bool) $this->hasLabel; - } - - /** - * Does this collector have information for the timeline? - */ - public function hasTimelineData(): bool - { - return (bool) $this->hasTimeline; - } - - /** - * Grabs the data for the timeline, properly formatted, - * or returns an empty array. - */ - public function timelineData(): array - { - if (! $this->hasTimeline) { - return []; - } - - return $this->formatTimelineData(); - } - - /** - * Does this Collector have data that should be shown in the - * 'Vars' tab? - */ - public function hasVarData(): bool - { - return (bool) $this->hasVarData; - } - - /** - * Gets a collection of data that should be shown in the 'Vars' tab. - * The format is an array of sections, each with their own array - * of key/value pairs: - * - * $data = [ - * 'section 1' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * 'section 2' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * ]; - * - * @return array|null - */ - public function getVarData() - { - return null; - } - - /** - * Child classes should implement this to return the timeline data - * formatted for correct usage. - * - * Timeline data should be formatted into arrays that look like: - * - * [ - * 'name' => 'Database::Query', - * 'component' => 'Database', - * 'start' => 10 // milliseconds - * 'duration' => 15 // milliseconds - * ] - */ - protected function formatTimelineData(): array - { - return []; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - * - * @return array|string - */ - public function display() - { - return []; - } - - /** - * This makes nicer looking paths for the error output. - * - * @deprecated Use the dedicated `clean_path()` function. - */ - public function cleanPath(string $file): string - { - return clean_path($file); - } - - /** - * Gets the "badge" value for the button. - * - * @return int|null - */ - public function getBadgeValue() - { - return null; - } - - /** - * Does this collector have any data collected? - * - * If not, then the toolbar button won't get shown. - */ - public function isEmpty(): bool - { - return false; - } - - /** - * Returns the HTML to display the icon. Should either - * be SVG, or a base-64 encoded. - * - * Recommended dimensions are 24px x 24px - */ - public function icon(): string - { - return ''; - } - - /** - * Return settings as an array. - */ - public function getAsArray(): array - { - return [ - 'title' => $this->getTitle(), - 'titleSafe' => $this->getTitle(true), - 'titleDetails' => $this->getTitleDetails(), - 'display' => $this->display(), - 'badgeValue' => $this->getBadgeValue(), - 'isEmpty' => $this->isEmpty(), - 'hasTabContent' => $this->hasTabContent(), - 'hasLabel' => $this->hasLabel(), - 'icon' => $this->icon(), - 'hasTimelineData' => $this->hasTimelineData(), - 'timelineData' => $this->timelineData(), - ]; - } -} diff --git a/src_DEPRICATED/Collectors/Config.php b/src_DEPRICATED/Collectors/Config.php deleted file mode 100644 index 74fdb09..0000000 --- a/src_DEPRICATED/Collectors/Config.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -use CodeIgniter\CodeIgniter; -use Config\App; - -/** - * Debug toolbar configuration - */ -class Config -{ - /** - * Return toolbar config values as an array. - */ - public static function display(): array - { - $config = config(App::class); - - return [ - 'ciVersion' => CodeIgniter::CI_VERSION, - 'phpVersion' => PHP_VERSION, - 'phpSAPI' => PHP_SAPI, - 'environment' => ENVIRONMENT, - 'baseURL' => $config->baseURL, - 'timezone' => app_timezone(), - 'locale' => service('request')->getLocale(), - 'cspEnabled' => $config->CSPEnabled, - ]; - } -} diff --git a/src_DEPRICATED/Collectors/Database.php b/src_DEPRICATED/Collectors/Database.php deleted file mode 100644 index 093f4d5..0000000 --- a/src_DEPRICATED/Collectors/Database.php +++ /dev/null @@ -1,266 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -use CodeIgniter\Database\Query; -use CodeIgniter\I18n\Time; -use Config\Toolbar; - -/** - * Collector for the Database tab of the Debug Toolbar. - * - * @see \Wizdam\DebugBar\Collectors\DatabaseTest - */ -class Database extends BaseCollector -{ - /** - * Whether this collector has timeline data. - * - * @var bool - */ - protected $hasTimeline = true; - - /** - * Whether this collector should display its own tab. - * - * @var bool - */ - protected $hasTabContent = true; - - /** - * Whether this collector has data for the Vars tab. - * - * @var bool - */ - protected $hasVarData = false; - - /** - * The name used to reference this collector in the toolbar. - * - * @var string - */ - protected $title = 'Database'; - - /** - * Array of database connections. - * - * @var array - */ - protected $connections; - - /** - * The query instances that have been collected - * through the DBQuery Event. - * - * @var array - */ - protected static $queries = []; - - /** - * Constructor - */ - public function __construct() - { - $this->getConnections(); - } - - /** - * The static method used during Events to collect - * data. - * - * @internal - * - * @return void - */ - public static function collect(Query $query) - { - $config = config(Toolbar::class); - - // Provide default in case it's not set - $max = $config->maxQueries ?: 100; - - if (count(static::$queries) < $max) { - $queryString = $query->getQuery(); - - $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - - if (! is_cli()) { - // when called in the browser, the first two trace arrays - // are from the DB event trigger, which are unneeded - $backtrace = array_slice($backtrace, 2); - } - - static::$queries[] = [ - 'query' => $query, - 'string' => $queryString, - 'duplicate' => in_array($queryString, array_column(static::$queries, 'string'), true), - 'trace' => $backtrace, - ]; - } - } - - /** - * Returns timeline data formatted for the toolbar. - * - * @return array The formatted data or an empty array. - */ - protected function formatTimelineData(): array - { - $data = []; - - foreach ($this->connections as $alias => $connection) { - // Connection Time - $data[] = [ - 'name' => 'Connecting to Database: "' . $alias . '"', - 'component' => 'Database', - 'start' => $connection->getConnectStart(), - 'duration' => $connection->getConnectDuration(), - ]; - } - - foreach (static::$queries as $query) { - $data[] = [ - 'name' => 'Query', - 'component' => 'Database', - 'start' => $query['query']->getStartTime(true), - 'duration' => $query['query']->getDuration(), - 'query' => $query['query']->debugToolbarDisplay(), - ]; - } - - return $data; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - */ - public function display(): array - { - return ['queries' => array_map(static function (array $query): array { - $isDuplicate = $query['duplicate'] === true; - - $firstNonSystemLine = ''; - - foreach ($query['trace'] as $index => &$line) { - // simplify file and line - if (isset($line['file'])) { - $line['file'] = clean_path($line['file']) . ':' . $line['line']; - unset($line['line']); - } else { - $line['file'] = '[internal function]'; - } - - // find the first trace line that does not originate from `system/` - if ($firstNonSystemLine === '' && ! str_contains($line['file'], 'SYSTEMPATH')) { - $firstNonSystemLine = $line['file']; - } - - // simplify function call - if (isset($line['class'])) { - $line['function'] = $line['class'] . $line['type'] . $line['function']; - unset($line['class'], $line['type']); - } - - if (strrpos($line['function'], '{closure}') === false) { - $line['function'] .= '()'; - } - - $line['function'] = str_repeat(chr(0xC2) . chr(0xA0), 8) . $line['function']; - - // add index numbering padded with nonbreaking space - $indexPadded = str_pad(sprintf('%d', $index + 1), 3, ' ', STR_PAD_LEFT); - $indexPadded = preg_replace('/\s/', chr(0xC2) . chr(0xA0), $indexPadded); - - $line['index'] = $indexPadded . str_repeat(chr(0xC2) . chr(0xA0), 4); - } - - return [ - 'hover' => $isDuplicate ? 'This query was called more than once.' : '', - 'class' => $isDuplicate ? 'duplicate' : '', - 'duration' => ((float) $query['query']->getDuration(5) * 1000) . ' ms', - 'sql' => $query['query']->debugToolbarDisplay(), - 'trace' => $query['trace'], - 'trace-file' => $firstNonSystemLine, - 'qid' => md5($query['query'] . Time::now()->format('0.u00 U')), - ]; - }, static::$queries)]; - } - - /** - * Gets the "badge" value for the button. - */ - public function getBadgeValue(): int - { - return count(static::$queries); - } - - /** - * Information to be displayed next to the title. - * - * @return string The number of queries (in parentheses) or an empty string. - */ - public function getTitleDetails(): string - { - $this->getConnections(); - - $queryCount = count(static::$queries); - $uniqueCount = count(array_filter(static::$queries, static fn ($query): bool => $query['duplicate'] === false)); - $connectionCount = count($this->connections); - - return sprintf( - '(%d total Quer%s, %d %s unique across %d Connection%s)', - $queryCount, - $queryCount > 1 ? 'ies' : 'y', - $uniqueCount, - $uniqueCount > 1 ? 'of them' : '', - $connectionCount, - $connectionCount > 1 ? 's' : '', - ); - } - - /** - * Does this collector have any data collected? - */ - public function isEmpty(): bool - { - return static::$queries === []; - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADMSURBVEhLY6A3YExLSwsA4nIycQDIDIhRWEBqamo/UNF/SjDQjF6ocZgAKPkRiFeEhoYyQ4WIBiA9QAuWAPEHqBAmgLqgHcolGQD1V4DMgHIxwbCxYD+QBqcKINseKo6eWrBioPrtQBq/BcgY5ht0cUIYbBg2AJKkRxCNWkDQgtFUNJwtABr+F6igE8olGQD114HMgHIxAVDyAhA/AlpSA8RYUwoeXAPVex5qHCbIyMgwBCkAuQJIY00huDBUz/mUlBQDqHGjgBjAwAAACexpph6oHSQAAAAASUVORK5CYII='; - } - - /** - * Gets the connections from the database config - */ - private function getConnections(): void - { - $this->connections = \Config\Database::getConnections(); - } - - /** - * Reset collector state for worker mode. - * Clears collected queries between requests. - */ - public function reset(): void - { - static::$queries = []; - } -} diff --git a/src_DEPRICATED/Collectors/Events.php b/src_DEPRICATED/Collectors/Events.php deleted file mode 100644 index bb38930..0000000 --- a/src_DEPRICATED/Collectors/Events.php +++ /dev/null @@ -1,125 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -/** - * Events collector - */ -class Events extends BaseCollector -{ - /** - * Whether this collector has data that can - * be displayed in the Timeline. - * - * @var bool - */ - protected $hasTimeline = true; - - /** - * Whether this collector needs to display - * content in a tab or not. - * - * @var bool - */ - protected $hasTabContent = true; - - /** - * Whether this collector has data that - * should be shown in the Vars tab. - * - * @var bool - */ - protected $hasVarData = false; - - /** - * The 'title' of this Collector. - * Used to name things in the toolbar HTML. - * - * @var string - */ - protected $title = 'Events'; - - /** - * Child classes should implement this to return the timeline data - * formatted for correct usage. - */ - protected function formatTimelineData(): array - { - $data = []; - - $rows = \CodeIgniter\Events\Events::getPerformanceLogs(); - - foreach ($rows as $info) { - $data[] = [ - 'name' => 'Event: ' . $info['event'], - 'component' => 'Events', - 'start' => $info['start'], - 'duration' => $info['end'] - $info['start'], - ]; - } - - return $data; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - */ - public function display(): array - { - $data = [ - 'events' => [], - ]; - - foreach (\CodeIgniter\Events\Events::getPerformanceLogs() as $row) { - $key = $row['event']; - - if (! array_key_exists($key, $data['events'])) { - $data['events'][$key] = [ - 'event' => $key, - 'duration' => ($row['end'] - $row['start']) * 1000, - 'count' => 1, - ]; - - continue; - } - - $data['events'][$key]['duration'] += ($row['end'] - $row['start']) * 1000; - $data['events'][$key]['count']++; - } - - foreach ($data['events'] as &$row) { - $row['duration'] = number_format($row['duration'], 2); - } - - return $data; - } - - /** - * Gets the "badge" value for the button. - */ - public function getBadgeValue(): int - { - return count(\CodeIgniter\Events\Events::getPerformanceLogs()); - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEASURBVEhL7ZXNDcIwDIVTsRBH1uDQDdquUA6IM1xgCA6MwJUN2hk6AQzAz0vl0ETUxC5VT3zSU5w81/mRMGZysixbFEVR0jSKNt8geQU9aRpFmp/keX6AbjZ5oB74vsaN5lSzA4tLSjpBFxsjeSuRy4d2mDdQTWU7YLbXTNN05mKyovj5KL6B7q3hoy3KwdZxBlT+Ipz+jPHrBqOIynZgcZonoukb/0ckiTHqNvDXtXEAaygRbaB9FvUTjRUHsIYS0QaSp+Dw6wT4hiTmYHOcYZsdLQ2CbXa4ftuuYR4x9vYZgdb4vsFYUdmABMYeukK9/SUme3KMFQ77+Yfzh8eYF8+orDuDWU5LAAAAAElFTkSuQmCC'; - } -} diff --git a/src_DEPRICATED/Collectors/Files.php b/src_DEPRICATED/Collectors/Files.php deleted file mode 100644 index 1e14a68..0000000 --- a/src_DEPRICATED/Collectors/Files.php +++ /dev/null @@ -1,104 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -/** - * Files collector - */ -class Files extends BaseCollector -{ - /** - * Whether this collector has data that can - * be displayed in the Timeline. - * - * @var bool - */ - protected $hasTimeline = false; - - /** - * Whether this collector needs to display - * content in a tab or not. - * - * @var bool - */ - protected $hasTabContent = true; - - /** - * The 'title' of this Collector. - * Used to name things in the toolbar HTML. - * - * @var string - */ - protected $title = 'Files'; - - /** - * Returns any information that should be shown next to the title. - */ - public function getTitleDetails(): string - { - return '( ' . count(get_included_files()) . ' )'; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - */ - public function display(): array - { - $rawFiles = get_included_files(); - $coreFiles = []; - $userFiles = []; - - foreach ($rawFiles as $file) { - $path = clean_path($file); - - if (str_contains($path, 'SYSTEMPATH')) { - $coreFiles[] = [ - 'path' => $path, - 'name' => basename($file), - ]; - } else { - $userFiles[] = [ - 'path' => $path, - 'name' => basename($file), - ]; - } - } - - sort($userFiles); - sort($coreFiles); - - return [ - 'coreFiles' => $coreFiles, - 'userFiles' => $userFiles, - ]; - } - - /** - * Displays the number of included files as a badge in the tab button. - */ - public function getBadgeValue(): int - { - return count(get_included_files()); - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGBSURBVEhL7ZQ9S8NQGIVTBQUncfMfCO4uLgoKbuKQOWg+OkXERRE1IAXrIHbVDrqIDuLiJgj+gro7S3dnpfq88b1FMTE3VZx64HBzzvvZWxKnj15QCcPwCD5HUfSWR+JtzgmtsUcQBEva5IIm9SwSu+95CAWbUuy67qBa32ByZEDpIaZYZSZMjjQuPcQUq8yEyYEb8FSerYeQVGbAFzJkX1PyQWLhgCz0BxTCekC1Wp0hsa6yokzhed4oje6Iz6rlJEkyIKfUEFtITVtQdAibn5rMyaYsMS+a5wTv8qeXMhcU16QZbKgl3hbs+L4/pnpdc87MElZgq10p5DxGdq8I7xrvUWUKvG3NbSK7ubngYzdJwSsF7TiOh9VOgfcEz1UayNe3JUPM1RWC5GXYgTfc75B4NBmXJnAtTfpABX0iPvEd9ezALwkplCFXcr9styiNOKc1RRZpaPM9tcqBwlWzGY1qPL9wjqRBgF5BH6j8HWh2S7MHlX8PrmbK+k/8PzjOOzx1D3i1pKTTAAAAAElFTkSuQmCC'; - } -} diff --git a/src_DEPRICATED/Collectors/History.php b/src_DEPRICATED/Collectors/History.php deleted file mode 100644 index 8305853..0000000 --- a/src_DEPRICATED/Collectors/History.php +++ /dev/null @@ -1,145 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -use DateTime; - -/** - * History collector - * - * @see \Wizdam\DebugBar\Collectors\HistoryTest - */ -class History extends BaseCollector -{ - /** - * Whether this collector has data that can - * be displayed in the Timeline. - * - * @var bool - */ - protected $hasTimeline = false; - - /** - * Whether this collector needs to display - * content in a tab or not. - * - * @var bool - */ - protected $hasTabContent = true; - - /** - * Whether this collector needs to display - * a label or not. - * - * @var bool - */ - protected $hasLabel = true; - - /** - * The 'title' of this Collector. - * Used to name things in the toolbar HTML. - * - * @var string - */ - protected $title = 'History'; - - /** - * @var array History files - */ - protected $files = []; - - /** - * Specify time limit & file count for debug history. - * - * @param string $current Current history time - * @param int $limit Max history files - * - * @return void - */ - public function setFiles(string $current, int $limit = 20) - { - $filenames = glob(WRITEPATH . 'debugbar/debugbar_*.json'); - - $files = []; - $counter = 0; - - foreach (array_reverse($filenames) as $filename) { - $counter++; - - // Oldest files will be deleted - if ($limit >= 0 && $counter > $limit) { - unlink($filename); - - continue; - } - - // Get the contents of this specific history request - $contents = file_get_contents($filename); - - $contents = @json_decode($contents); - if (json_last_error() === JSON_ERROR_NONE) { - preg_match('/debugbar_(.*)\.json$/s', $filename, $time); - $time = sprintf('%.6F', $time[1] ?? 0); - - // Debugbar files shown in History Collector - $files[] = [ - 'time' => $time, - 'datetime' => DateTime::createFromFormat('U.u', $time)->format('Y-m-d H:i:s.u'), - 'active' => $time === $current, - 'status' => $contents->vars->response->statusCode, - 'method' => $contents->method, - 'url' => $contents->url, - 'isAJAX' => $contents->isAJAX ? 'Yes' : 'No', - 'contentType' => $contents->vars->response->contentType, - ]; - } - } - - $this->files = $files; - } - - /** - * Returns the data of this collector to be formatted in the toolbar - */ - public function display(): array - { - return ['files' => $this->files]; - } - - /** - * Displays the number of included files as a badge in the tab button. - */ - public function getBadgeValue(): int - { - return count($this->files); - } - - /** - * Return true if there are no history files. - */ - public function isEmpty(): bool - { - return $this->files === []; - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJySURBVEhL3ZU7aJNhGIVTpV6i4qCIgkIHxcXLErS4FBwUFNwiCKGhuTYJGaIgnRoo4qRu6iCiiIuIXXTTIkIpuqoFwaGgonUQlC5KafU5ycmNP0lTdPLA4fu+8573/a4/f6hXpFKpwUwmc9fDfweKbk+n07fgEv33TLSbtt/hvwNFT1PsG/zdTE0Gp+GFfD6/2fbVIxqNrqPIRbjg4t/hY8aztcngfDabHXbKyiiXy2vcrcPH8oDCry2FKDrA+Ar6L01E/ypyXzXaARjDGGcoeNxSDZXE0dHRA5VRE5LJ5CFy5jzJuOX2wHRHRnjbklZ6isQ3tIctBaAd4vlK3jLtkOVWqABBXd47jGHLmjTmSScttQV5J+SjfcUweFQEbsjAas5aqoCLXutJl7vtQsAzpRowYqkBinyCC8Vicb2lOih8zoldd0F8RD7qTFiqAnGrAy8stUAvi/hbqDM+YzkAFrLPdR5ZqoLXsd+Bh5YCIH7JniVdquUWxOPxDfboHhrI5XJ7HHhiqQXox+APe/Qk64+gGYVCYZs8cMpSFQj9JOoFzVqqo7k4HIvFYpscCoAjOmLffUsNUGRaQUwDlmofUa34ecsdgXdcXo4wbakBgiUFafXJV8A4DJ/2UrxUKm3E95H8RbjLcgOJRGILhnmCP+FBy5XvwN2uIPcy1AJvWgqC4xm2aU4Xb3lF4I+Tpyf8hRe5w3J7YLymSeA8Z3nSclv4WLRyFdfOjzrUFX0klJUEtZtntCNc+F69cz/FiDzEPtjzmcUMOr83kDQEX6pAJxJfpL3OX22n01YN7SZCoQnaSdoZ+Jz+PZihH3wt/xlCoT9M6nEtmRSPCQAAAABJRU5ErkJggg=='; - } -} diff --git a/src_DEPRICATED/Collectors/Logs.php b/src_DEPRICATED/Collectors/Logs.php deleted file mode 100644 index 45aa144..0000000 --- a/src_DEPRICATED/Collectors/Logs.php +++ /dev/null @@ -1,101 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -/** - * Loags collector - */ -class Logs extends BaseCollector -{ - /** - * Whether this collector has data that can - * be displayed in the Timeline. - * - * @var bool - */ - protected $hasTimeline = false; - - /** - * Whether this collector needs to display - * content in a tab or not. - * - * @var bool - */ - protected $hasTabContent = true; - - /** - * The 'title' of this Collector. - * Used to name things in the toolbar HTML. - * - * @var string - */ - protected $title = 'Logs'; - - /** - * Our collected data. - * - * @var list - */ - protected $data = []; - - /** - * Returns the data of this collector to be formatted in the toolbar. - * - * @return array{logs: list} - */ - public function display(): array - { - return [ - 'logs' => $this->collectLogs(), - ]; - } - - /** - * Does this collector actually have any data to display? - */ - public function isEmpty(): bool - { - $this->collectLogs(); - - return $this->data === []; - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAACYSURBVEhLYxgFJIHU1FSjtLS0i0D8AYj7gEKMEBkqAaAFF4D4ERCvAFrwH4gDoFIMKSkpFkB+OTEYqgUTACXfA/GqjIwMQyD9H2hRHlQKJFcBEiMGQ7VgAqCBvUgK32dmZspCpagGGNPT0/1BLqeF4bQHQJePpiIwhmrBBEADR1MRfgB0+WgqAmOoFkwANHA0FY0CUgEDAwCQ0PUpNB3kqwAAAABJRU5ErkJggg=='; - } - - /** - * Ensures the data has been collected. - * - * @return list - */ - protected function collectLogs() - { - if ($this->data !== []) { - return $this->data; - } - - $cache = service('logger')->logCache; - - $this->data = $cache ?? []; - - return $this->data; - } -} diff --git a/src_DEPRICATED/Collectors/Routes.php b/src_DEPRICATED/Collectors/Routes.php deleted file mode 100644 index ee766c8..0000000 --- a/src_DEPRICATED/Collectors/Routes.php +++ /dev/null @@ -1,169 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -use CodeIgniter\Router\DefinedRouteCollector; -use ReflectionException; -use ReflectionFunction; -use ReflectionMethod; - -/** - * Routes collector - */ -class Routes extends BaseCollector -{ - /** - * Whether this collector has data that can - * be displayed in the Timeline. - * - * @var bool - */ - protected $hasTimeline = false; - - /** - * Whether this collector needs to display - * content in a tab or not. - * - * @var bool - */ - protected $hasTabContent = true; - - /** - * The 'title' of this Collector. - * Used to name things in the toolbar HTML. - * - * @var string - */ - protected $title = 'Routes'; - - /** - * Returns the data of this collector to be formatted in the toolbar - * - * @return array{ - * matchedRoute: list - * }>, - * routes: list - * } - * - * @throws ReflectionException - */ - public function display(): array - { - $rawRoutes = service('routes', true); - $router = service('router', null, null, true); - - // Get our parameters - // Closure routes - if (is_callable($router->controllerName())) { - $method = new ReflectionFunction($router->controllerName()); - } else { - try { - $method = new ReflectionMethod($router->controllerName(), $router->methodName()); - } catch (ReflectionException) { - try { - // If we're here, the method doesn't exist - // and is likely calculated in _remap. - $method = new ReflectionMethod($router->controllerName(), '_remap'); - } catch (ReflectionException) { - // If we're here, page cache is returned. The router is not executed. - return [ - 'matchedRoute' => [], - 'routes' => [], - ]; - } - } - } - - $rawParams = $method->getParameters(); - - $params = []; - - foreach ($rawParams as $key => $param) { - $params[] = [ - 'name' => '$' . $param->getName() . ' = ', - 'value' => $router->params()[$key] ?? - ' | default: ' - . var_export( - $param->isDefaultValueAvailable() ? $param->getDefaultValue() : null, - true, - ), - ]; - } - - $matchedRoute = [ - [ - 'directory' => $router->directory(), - 'controller' => $router->controllerName(), - 'method' => $router->methodName(), - 'paramCount' => count($router->params()), - 'truePCount' => count($params), - 'params' => $params, - ], - ]; - - // Defined Routes - $routes = []; - - $definedRouteCollector = new DefinedRouteCollector($rawRoutes); - - foreach ($definedRouteCollector->collect() as $route) { - // filter for strings, as callbacks aren't displayable - if ($route['handler'] !== '(Closure)') { - $routes[] = [ - 'method' => strtoupper($route['method']), - 'route' => $route['route'], - 'handler' => $route['handler'], - ]; - } - } - - return [ - 'matchedRoute' => $matchedRoute, - 'routes' => $routes, - ]; - } - - /** - * Returns a count of all the routes in the system. - */ - public function getBadgeValue(): int - { - $rawRoutes = service('routes', true); - - return count($rawRoutes->getRoutes()); - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFDSURBVEhL7ZRNSsNQFIUjVXSiOFEcuQIHDpzpxC0IGYeE/BEInbWlCHEDLsSiuANdhKDjgm6ggtSJ+l25ldrmmTwIgtgDh/t37r1J+16cX0dRFMtpmu5pWAkrvYjjOB7AETzStBFW+inxu3KUJMmhludQpoflS1zXban4LYqiO224h6VLTHr8Z+z8EpIHFF9gG78nDVmW7UgTHKjsCyY98QP+pcq+g8Ku2s8G8X3f3/I8b038WZTp+bO38zxfFd+I6YY6sNUvFlSDk9CRhiAI1jX1I9Cfw7GG1UB8LAuwbU0ZwQnbRDeEN5qqBxZMLtE1ti9LtbREnMIuOXnyIf5rGIb7Wq8HmlZgwYBH7ORTcKH5E4mpjeGt9fBZcHE2GCQ3Vt7oTNPNg+FXLHnSsHkw/FR+Gg2bB8Ptzrst/v6C/wrH+QB+duli6MYJdQAAAABJRU5ErkJggg=='; - } -} diff --git a/src_DEPRICATED/Collectors/Timers.php b/src_DEPRICATED/Collectors/Timers.php deleted file mode 100644 index ca5dc6b..0000000 --- a/src_DEPRICATED/Collectors/Timers.php +++ /dev/null @@ -1,71 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -/** - * Timers collector - */ -class Timers extends BaseCollector -{ - /** - * Whether this collector has data that can - * be displayed in the Timeline. - * - * @var bool - */ - protected $hasTimeline = true; - - /** - * Whether this collector needs to display - * content in a tab or not. - * - * @var bool - */ - protected $hasTabContent = false; - - /** - * The 'title' of this Collector. - * Used to name things in the toolbar HTML. - * - * @var string - */ - protected $title = 'Timers'; - - /** - * Child classes should implement this to return the timeline data - * formatted for correct usage. - */ - protected function formatTimelineData(): array - { - $data = []; - - $benchmark = service('timer', true); - $rows = $benchmark->getTimers(6); - - foreach ($rows as $name => $info) { - if ($name === 'total_execution') { - continue; - } - - $data[] = [ - 'name' => ucwords(str_replace('_', ' ', $name)), - 'component' => 'Timer', - 'start' => $info['start'], - 'duration' => $info['end'] - $info['start'], - ]; - } - - return $data; - } -} diff --git a/src_DEPRICATED/Collectors/Views.php b/src_DEPRICATED/Collectors/Views.php deleted file mode 100644 index 1ef8ecd..0000000 --- a/src_DEPRICATED/Collectors/Views.php +++ /dev/null @@ -1,150 +0,0 @@ - - * - * For the full copyright and license information, please view - * the LICENSE file that was distributed with this source code. - */ - -namespace Wizdam\DebugBar\Collectors; - -use CodeIgniter\View\RendererInterface; - -/** - * Views collector - */ -class Views extends BaseCollector -{ - /** - * Whether this collector has data that can - * be displayed in the Timeline. - * - * @var bool - */ - protected $hasTimeline = true; - - /** - * Whether this collector needs to display - * content in a tab or not. - * - * @var bool - */ - protected $hasTabContent = false; - - /** - * Whether this collector needs to display - * a label or not. - * - * @var bool - */ - protected $hasLabel = true; - - /** - * Whether this collector has data that - * should be shown in the Vars tab. - * - * @var bool - */ - protected $hasVarData = true; - - /** - * The 'title' of this Collector. - * Used to name things in the toolbar HTML. - * - * @var string - */ - protected $title = 'Views'; - - /** - * Instance of the shared Renderer service - * - * @var RendererInterface|null - */ - protected $viewer; - - /** - * Views counter - * - * @var array - */ - protected $views = []; - - private function initViewer(): void - { - $this->viewer ??= service('renderer'); - } - - /** - * Child classes should implement this to return the timeline data - * formatted for correct usage. - */ - protected function formatTimelineData(): array - { - $this->initViewer(); - - $data = []; - - $rows = $this->viewer->getPerformanceData(); - - foreach ($rows as $info) { - $data[] = [ - 'name' => 'View: ' . $info['view'], - 'component' => 'Views', - 'start' => $info['start'], - 'duration' => $info['end'] - $info['start'], - ]; - } - - return $data; - } - - /** - * Gets a collection of data that should be shown in the 'Vars' tab. - * The format is an array of sections, each with their own array - * of key/value pairs: - * - * $data = [ - * 'section 1' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * 'section 2' => [ - * 'foo' => 'bar, - * 'bar' => 'baz' - * ], - * ]; - */ - public function getVarData(): array - { - $this->initViewer(); - - return [ - 'View Data' => $this->viewer->getData(), - ]; - } - - /** - * Returns a count of all views. - */ - public function getBadgeValue(): int - { - $this->initViewer(); - - return count($this->viewer->getPerformanceData()); - } - - /** - * Display the icon. - * - * Icon from https://icons8.com - 1em package - */ - public function icon(): string - { - return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADeSURBVEhL7ZSxDcIwEEWNYA0YgGmgyAaJLTcUaaBzQQEVjMEabBQxAdw53zTHiThEovGTfnE/9rsoRUxhKLOmaa6Uh7X2+UvguLCzVxN1XW9x4EYHzik033Hp3X0LO+DaQG8MDQcuq6qao4qkHuMgQggLvkPLjqh00ZgFDBacMJYFkuwFlH1mshdkZ5JPJERA9JpI6xNCBESvibQ+IURA9JpI6xNCBESvibQ+IURA9DTsuHTOrVFFxixgB/eUFlU8uKJ0eDBFOu/9EvoeKnlJS2/08Tc8NOwQ8sIfMeYFjqKDjdU2sp4AAAAASUVORK5CYII='; - } -} diff --git a/src_DEPRICATED/DebugBar.php b/src_DEPRICATED/DebugBar.php deleted file mode 100644 index 4395fa5..0000000 --- a/src_DEPRICATED/DebugBar.php +++ /dev/null @@ -1,561 +0,0 @@ - */ - protected array $collectors = []; - - public function __construct(array $config = []) - { - $defaults = require __DIR__ . '/../config/debugbar.php'; - $this->config = array_merge($defaults, $config); - - foreach ($this->config['collectors'] as $collectorClass) { - if (! class_exists($collectorClass)) { - error_log('WizdamDebugBar: Collector tidak ditemukan (' . $collectorClass . ').'); - continue; - } - $this->collectors[] = new $collectorClass(); - } - } - - // --------------------------------------------------------------- - // Data collection - // --------------------------------------------------------------- - - /** - * Kumpulkan semua data dan kembalikan sebagai JSON. - * Dipanggil di akhir siklus request, sebelum response dikirim. - * - * Menggantikan: Toolbar::run(float, float, RequestInterface, ResponseInterface) - * Perubahan: request/response CI4 diganti dengan superglobal PHP. - */ - public function run(float $startTime, float $totalTime): string - { - $data = []; - - $data['url'] = $this->currentUrl(); - $data['method'] = strtoupper($_SERVER['REQUEST_METHOD'] ?? 'GET'); - $data['isAJAX'] = $this->isAjax(); - $data['startTime'] = $startTime; - $data['totalTime'] = $totalTime * 1000; - $data['totalMemory'] = number_format(memory_get_peak_usage() / 1024 / 1024, 3); - $data['segmentDuration'] = $this->roundTo($data['totalTime'] / 7); - $data['segmentCount'] = (int) ceil($data['totalTime'] / max($data['segmentDuration'], 0.001)); - $data['DEBUGBAR_VERSION'] = self::VERSION; - $data['collectors'] = []; - - foreach ($this->collectors as $collector) { - $data['collectors'][] = $collector->getAsArray(); - } - - // --- Var Data dari collector --- - foreach ($this->collectVarData() as $heading => $items) { - $varData = []; - if (is_array($items)) { - foreach ($items as $key => $value) { - $varData[$this->esc((string) $key)] = is_string($value) - ? $this->esc($value) - : '
' . $this->esc(print_r($value, true)) . '
'; - } - } - $data['vars']['varData'][$this->esc((string) $heading)] = $varData; - } - - // --- Session --- - if (isset($_SESSION)) { - foreach ($_SESSION as $key => $value) { - if (is_string($value) && preg_match('~[^\x20-\x7E\t\r\n]~', $value)) { - $value = 'binary data'; - } - $data['vars']['session'][$this->esc((string) $key)] = is_string($value) - ? $this->esc($value) - : '
' . $this->esc(print_r($value, true)) . '
'; - } - } - - // --- GET --- - foreach ($_GET as $name => $value) { - $data['vars']['get'][$this->esc((string) $name)] = is_array($value) - ? '
' . $this->esc(print_r($value, true)) . '
' - : $this->esc((string) $value); - } - - // --- POST --- - foreach ($_POST as $name => $value) { - $data['vars']['post'][$this->esc((string) $name)] = is_array($value) - ? '
' . $this->esc(print_r($value, true)) . '
' - : $this->esc((string) $value); - } - - // --- Request headers --- - foreach ($this->getRequestHeaders() as $name => $value) { - $data['vars']['headers'][$this->esc($name)] = $this->esc($value); - } - - // --- Cookies --- - foreach ($_COOKIE as $name => $value) { - $data['vars']['cookies'][$this->esc((string) $name)] = $this->esc((string) $value); - } - - // --- Request protocol --- - $isSecure = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ($_SERVER['SERVER_PORT'] ?? '') === '443'; - $proto = $isSecure ? 'HTTPS' : 'HTTP'; - $serverVer = explode('/', $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1')[1] ?? '1.1'; - $data['vars']['request'] = $proto . '/' . $serverVer; - - // --- Response --- - $statusCode = http_response_code() ?: 200; - $data['vars']['response'] = [ - 'statusCode' => $statusCode, - 'reason' => $this->getStatusReason($statusCode), - 'contentType' => $this->esc($this->getResponseContentType()), - 'headers' => [], - ]; - foreach (headers_list() as $header) { - $parts = explode(':', $header, 2); - if (count($parts) === 2) { - $data['vars']['response']['headers'][$this->esc(trim($parts[0]))] = $this->esc(trim($parts[1])); - } - } - - // --- Config tab --- - $data['config'] = Config::display($this->config); - - return json_encode($data); - } - - /** - * Simpan data JSON ke file history, lalu inject script loader ke response. - * - * Menggantikan: Toolbar::prepare() - * Perubahan: tidak ada dependency Services::, tidak ada Kint, tidak ada CSP nonce. - */ - public function prepare(string &$responseBody, string $contentType = 'text/html'): void - { - if (PHP_SAPI === 'cli') { - return; - } - - if ($this->hasNativeHeaderConflict()) { - return; - } - - if ($this->shouldDisableToolbar()) { - return; - } - - if (! str_contains($contentType, 'html')) { - return; - } - - $startTime = $this->config['startTime'] ?? $_SERVER['REQUEST_TIME_FLOAT'] ?? microtime(true); - $totalTime = microtime(true) - $startTime; - $data = $this->run((float) $startTime, $totalTime); - - $historyPath = rtrim($this->config['historyPath'], '/') . '/'; - if (! is_dir($historyPath)) { - mkdir($historyPath, 0777, true); - } - - $time = sprintf('%.6F', microtime(true)); - $filename = $historyPath . 'debugbar_' . $time . '.json'; - file_put_contents($filename, $data); - - $baseURL = rtrim($this->config['baseURL'], '/'); - $script = PHP_EOL - . '' - . '' - . '' - . PHP_EOL; - - if (str_contains($responseBody, '')) { - $responseBody = preg_replace('//', '' . $script, $responseBody, 1); - } else { - $responseBody .= $script; - } - } - - /** - * Tangani request AJAX toolbar (loader JS dan data JSON). - * - * Menggantikan: Toolbar::respond() - * Perubahan: tidak ada CI4 service(), tidak ada helper('security'). - */ - public function respond(): bool - { - parse_str($_SERVER['QUERY_STRING'] ?? '', $queryParams); - - // Serve toolbar loader JS - if (array_key_exists('debugbar', $queryParams)) { - header('Content-Type: application/javascript'); - $loaderFile = $this->config['viewsPath'] . 'toolbarloader.js'; - $output = file_exists($loaderFile) ? file_get_contents($loaderFile) : ''; - $output = str_replace('{url}', rtrim($this->config['baseURL'], '/'), $output); - echo $output; - return true; - } - - // Serve toolbar data untuk waktu request tertentu - if (! empty($queryParams['debugbar_time'])) { - $time = $this->sanitizeFilename((string) $queryParams['debugbar_time']); - $filepath = rtrim($this->config['historyPath'], '/') . '/debugbar_' . $time . '.json'; - - if (is_file($filepath)) { - $format = $this->negotiateFormat(); - echo $this->format(file_get_contents($filepath), $format); - return true; - } - - http_response_code(404); - return true; - } - - return false; - } - - // --------------------------------------------------------------- - // Rendering - // --------------------------------------------------------------- - - /** - * Format dan render toolbar. - * - * Menggantikan: Toolbar::format() - * Perubahan: service('parser') diganti dengan PHP include + extract(). - */ - protected function format(string $jsonData, string $format = 'html'): string - { - $data = json_decode($jsonData, true); - - // Tambahkan History collector - parse_str($_SERVER['QUERY_STRING'] ?? '', $queryParams); - if (! empty($queryParams['debugbar_time']) - && preg_match('/\d+\.\d{6}/s', $queryParams['debugbar_time'], $match) - ) { - $history = new History($this->config); - $history->setFiles($match[0], $this->config['maxHistory']); - $data['collectors'][] = $history->getAsArray(); - } - - if ($format === 'html') { - $data['styles'] = []; - $DEBUGBAR_VERSION = self::VERSION; - $baseURL = rtrim($this->config['baseURL'], '/'); - $viewsPath = $this->config['viewsPath']; - $debugBar = $this; - - extract($data); - - ob_start(); - include $this->config['viewsPath'] . 'toolbar.tpl.php'; - return (string) ob_get_clean(); - } - - if ($format === 'json') { - header('Content-Type: application/json'); - return json_encode($data, JSON_PRETTY_PRINT); - } - - return ''; - } - - /** - * Render timeline HTML. - * - * Identik dengan CI4, dipindahkan ke sini agar bisa dipanggil - * dari toolbar.tpl.php via $debugBar->renderTimeline(). - */ - public function renderTimeline( - array $collectors, - float $startTime, - int $segmentCount, - int $segmentDuration, - array &$styles - ): string { - $rows = $this->collectTimelineData($collectors); - $styleCount = 0; - return $this->renderTimelineRecursive($rows, $startTime, $segmentCount, $segmentDuration, $styles, $styleCount); - } - - protected function renderTimelineRecursive( - array $rows, - float $startTime, - int $segmentCount, - int $segmentDuration, - array &$styles, - int &$styleCount, - int $level = 0, - bool $isChild = false - ): string { - $displayTime = $segmentCount * $segmentDuration; - $output = ''; - - foreach ($rows as $row) { - $hasChildren = isset($row['children']) && ! empty($row['children']); - $isQuery = isset($row['query']) && ! empty($row['query']); - $open = $row['name'] === 'Controller'; - - if ($hasChildren || $isQuery) { - $output .= ''; - } else { - $output .= ''; - } - - $output .= '' - . ($hasChildren || $isQuery ? '' : '') . $row['name'] . ''; - $output .= '' . $row['component'] . ''; - $output .= '' - . number_format($row['duration'] * 1000, 2) . ' ms'; - $output .= ""; - - $offset = ((((float) $row['start'] - $startTime) * 1000) / $displayTime) * 100; - $length = (((float) $row['duration'] * 1000) / $displayTime) * 100; - - $styles['debug-bar-timeline-' . $styleCount] = "left: {$offset}%; width: {$length}%;"; - - $output .= ""; - - $styleCount++; - - if ($hasChildren || $isQuery) { - $output .= ''; - $output .= ''; - - if ($isQuery) { - $output .= ''; - } else { - $output .= $this->renderTimelineRecursive($row['children'], $startTime, $segmentCount, $segmentDuration, $styles, $styleCount, $level + 1, true); - } - - $output .= '
' . $row['query'] . '
'; - } - } - - return $output; - } - - protected function collectTimelineData(array $collectors): array - { - $data = []; - foreach ($collectors as $collector) { - if (! $collector['hasTimelineData']) { - continue; - } - $data = array_merge($data, $collector['timelineData']); - } - - $sortArray = [ - array_column($data, 'start'), SORT_NUMERIC, SORT_ASC, - array_column($data, 'duration'), SORT_NUMERIC, SORT_DESC, - &$data, - ]; - array_multisort(...$sortArray); - - array_walk($data, static function (&$row): void { - $row['end'] = $row['start'] + $row['duration']; - }); - - return $this->structureTimelineData($data); - } - - protected function structureTimelineData(array $elements): array - { - $element = array_shift($elements); - - while ($elements !== [] && $elements[array_key_first($elements)]['end'] <= $element['end']) { - $element['children'][] = array_shift($elements); - } - - if (isset($element['children'])) { - $element['children'] = $this->structureTimelineData($element['children']); - } - - if ($elements === []) { - return [$element]; - } - - return array_merge([$element], $this->structureTimelineData($elements)); - } - - protected function collectVarData(): array - { - if (! ($this->config['collectVarData'] ?? true)) { - return []; - } - - $data = []; - foreach ($this->collectors as $collector) { - if (! $collector->hasVarData()) { - continue; - } - $data = array_merge($data, $collector->getVarData()); - } - return $data; - } - - /** - * Render partial template untuk setiap collector tab. - * - * Menggantikan: $parser->setData($display)->render('_database.tpl') - * Dipanggil dari toolbar.tpl.php. - */ - public function renderPartial(string $viewsPath, string $template, array $data): string - { - extract($data); - ob_start(); - include $viewsPath . $template; - return (string) ob_get_clean(); - } - - // --------------------------------------------------------------- - // Helper methods - // --------------------------------------------------------------- - - public function esc(string $value): string - { - return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); - } - - protected function currentUrl(): string - { - $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; - $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; - $uri = $_SERVER['REQUEST_URI'] ?? '/'; - return $scheme . '://' . $host . $uri; - } - - protected function isAjax(): bool - { - return isset($_SERVER['HTTP_X_REQUESTED_WITH']) - && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; - } - - protected function shouldDisableToolbar(): bool - { - $disableOn = $this->config['disableOnHeaders'] ?? ['X-Requested-With' => 'xmlhttprequest']; - - foreach ($disableOn as $headerName => $expectedValue) { - $serverKey = 'HTTP_' . strtoupper(str_replace('-', '_', $headerName)); - if (! isset($_SERVER[$serverKey])) { - continue; - } - if ($expectedValue === null) { - return true; - } - if (strtolower($_SERVER[$serverKey]) === strtolower($expectedValue)) { - return true; - } - } - - return false; - } - - protected function hasNativeHeaderConflict(): bool - { - if (headers_sent()) { - return true; - } - - foreach (headers_list() as $header) { - $lower = strtolower($header); - if (str_starts_with($lower, 'content-type:') && ! str_contains($lower, 'text/html')) { - return true; - } - if (str_starts_with($lower, 'content-disposition:') && str_contains($lower, 'attachment')) { - return true; - } - } - - return false; - } - - protected function getRequestHeaders(): array - { - if (function_exists('getallheaders')) { - return getallheaders() ?: []; - } - - $headers = []; - foreach ($_SERVER as $key => $value) { - if (str_starts_with($key, 'HTTP_')) { - $name = str_replace('_', '-', substr($key, 5)); - $name = ucwords(strtolower($name), '-'); - $headers[$name] = (string) $value; - } - } - return $headers; - } - - protected function getResponseContentType(): string - { - foreach (headers_list() as $header) { - if (stripos($header, 'Content-Type:') === 0) { - return trim(substr($header, 13)); - } - } - return 'text/html'; - } - - protected function getStatusReason(int $code): string - { - $reasons = [ - 200 => 'OK', 201 => 'Created', 204 => 'No Content', - 301 => 'Moved Permanently', 302 => 'Found', 304 => 'Not Modified', - 400 => 'Bad Request', 401 => 'Unauthorized', 403 => 'Forbidden', - 404 => 'Not Found', 405 => 'Method Not Allowed', 422 => 'Unprocessable Entity', - 500 => 'Internal Server Error', 502 => 'Bad Gateway', 503 => 'Service Unavailable', - ]; - return $reasons[$code] ?? 'Unknown'; - } - - protected function sanitizeFilename(string $filename): string - { - return preg_replace('/[^a-zA-Z0-9._-]/', '', $filename); - } - - protected function negotiateFormat(): string - { - $accept = strtolower($_SERVER['HTTP_ACCEPT'] ?? ''); - if (str_contains($accept, 'application/json')) { - return 'json'; - } - return 'html'; - } - - protected function roundTo(float $number, int $increments = 5): float - { - $increments = 1 / $increments; - return ceil($number * $increments) / $increments; - } - - public function reset(): void - { - foreach ($this->collectors as $collector) { - if (method_exists($collector, 'reset')) { - $collector->reset(); - } - } - } -} \ No newline at end of file diff --git a/src_DEPRICATED/Interfaces/CollectorInterface.php b/src_DEPRICATED/Interfaces/CollectorInterface.php deleted file mode 100644 index f692bc2..0000000 --- a/src_DEPRICATED/Interfaces/CollectorInterface.php +++ /dev/null @@ -1,44 +0,0 @@ - string Query SQL yang dijalankan - * - 'duration' => float Waktu eksekusi dalam milidetik - * - 'params' => array Bind parameter (jika ada) - * - 'trace' => string Stack trace singkat (opsional) - */ - public function getQueries(): array; - - /** - * Total waktu eksekusi semua query dalam milidetik. - */ - public function getTotalTime(): float; - - /** - * Query yang dieksekusi lebih dari satu kali (duplikat). - * Return array of ['sql' => string, 'count' => int]. - */ - public function getDuplicates(): array; - - /** - * Jumlah total query yang dieksekusi. - */ - public function getQueryCount(): int; -} \ No newline at end of file diff --git a/src_DEPRICATED/Interfaces/RouterInterface.php b/src_DEPRICATED/Interfaces/RouterInterface.php deleted file mode 100644 index feae66c..0000000 --- a/src_DEPRICATED/Interfaces/RouterInterface.php +++ /dev/null @@ -1,47 +0,0 @@ - string Nama/path file template - * - 'duration' => float Waktu render dalam milidetik - * - 'data' => array Variabel yang diteruskan ke template - */ - public function getRenderedViews(): array; - - /** - * Total waktu render semua view dalam milidetik. - */ - public function getTotalRenderTime(): float; -} \ No newline at end of file diff --git a/src_DEPRICATED/Middleware/DebugToolbarMiddleware.php b/src_DEPRICATED/Middleware/DebugToolbarMiddleware.php deleted file mode 100644 index d88c9eb..0000000 --- a/src_DEPRICATED/Middleware/DebugToolbarMiddleware.php +++ /dev/null @@ -1,180 +0,0 @@ -startBuffer(); - * - * // ... eksekusi OJS normal ... - * - * $middleware->endBuffer(); // inject toolbar & flush output - * - * MODE 2 โ€” PSR-15 (untuk aplikasi modern dengan PSR-7/PSR-15) - * --------------------------------------------------------------- - * // Di stack middleware PSR-15: - * $app->add(new DebugToolbarMiddleware($debugBar)); - * - * // Implements process(ServerRequestInterface, RequestHandlerInterface) - * --- - */ -class DebugToolbarMiddleware -{ - private DebugBar $debugBar; - - /** Ekstensi Content-Type yang boleh di-inject toolbar */ - private array $allowedContentTypes = [ - 'text/html', - 'application/xhtml+xml', - ]; - - public function __construct(DebugBar $debugBar) - { - $this->debugBar = $debugBar; - } - - // --------------------------------------------------------------- - // MODE 1: Output Buffering (untuk OJS) - // --------------------------------------------------------------- - - /** - * Mulai menangkap output. - * Panggil di awal eksekusi aplikasi. - */ - public function startBuffer(): void - { - ob_start(); - } - - /** - * Ambil output yang sudah ditangkap, inject toolbar, lalu flush. - * Panggil di akhir eksekusi aplikasi. - */ - public function endBuffer(): void - { - $output = ob_get_clean(); - - if ($output === false) { - return; - } - - if ($this->shouldInject($output)) { - $output = $this->inject($output); - } - - echo $output; - } - - // --------------------------------------------------------------- - // MODE 2: PSR-15 (jika framework mendukung) - // --------------------------------------------------------------- - - /** - * Process request (PSR-15 style). - * Dapat digunakan tanpa dependensi PSR-15 formal โ€” - * cukup panggil secara manual dengan callable handler. - * - * @param callable $handler fn(array $request): string โ€” harus return HTML - */ - public function process(array $request, callable $handler): string - { - $response = $handler($request); - - if ($this->shouldInject($response)) { - $response = $this->inject($response); - } - - return $response; - } - - // --------------------------------------------------------------- - // Core injection - // --------------------------------------------------------------- - - /** - * Inject toolbar HTML ke dalam response sebelum tag . - */ - private function inject(string $response): string - { - $toolbarHtml = $this->debugBar->render(); - - // Inject tepat sebelum agar tidak mengganggu layout - if (stripos($response, '') !== false) { - return str_ireplace('', $toolbarHtml . '', $response); - } - - // Fallback: tambahkan di akhir jika tidak ada - return $response . $toolbarHtml; - } - - /** - * Cek apakah response layak di-inject. - * Tidak inject pada AJAX, JSON, binary, atau redirect. - */ - private function shouldInject(string $response): bool - { - // Jangan inject jika bukan request browser - if ($this->isAjaxRequest()) { - return false; - } - - // Jangan inject jika response tidak mengandung HTML - if (stripos(ltrim($response), '<') !== 0 && stripos($response, 'getResponseContentType(); - if ($contentType !== null && ! $this->isAllowedContentType($contentType)) { - return false; - } - - return true; - } - - private function isAjaxRequest(): bool - { - return ( - isset($_SERVER['HTTP_X_REQUESTED_WITH']) - && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest' - ); - } - - private function getResponseContentType(): ?string - { - foreach (headers_list() as $header) { - if (stripos($header, 'Content-Type:') === 0) { - return trim(explode(';', explode(':', $header, 2)[1])[0]); - } - } - - return null; - } - - private function isAllowedContentType(string $contentType): bool - { - foreach ($this->allowedContentTypes as $allowed) { - if (stripos($contentType, $allowed) !== false) { - return true; - } - } - - return false; - } -} \ No newline at end of file diff --git a/views/_config.tpl b/views/_config.tpl index e3235ec..bb2d21b 100644 --- a/views/_config.tpl +++ b/views/_config.tpl @@ -1,48 +1,48 @@ -

- Read the CodeIgniter docs... -

- + - - + + - + - + + + + + - + - - - - - - - - - +
CodeIgniter Version:{ ciVersion }WizdamDebugToolbar Version:
PHP Version:{ phpVersion }
PHP SAPI:{ phpSAPI }
Server OS:
Environment:{ environment }
Base URL: - { if $baseURL == '' } -
- The $baseURL should always be set manually to prevent possible URL personification from external parties. -
- { else } - { baseURL } - { endif } + + baseURL not configured. + + +
Timezone:{ timezone }
Locale:{ locale }
Content Security Policy Enabled:{ if $cspEnabled } Yes { else } No { endif }
diff --git a/views/_database.tpl b/views/_database.tpl index 054dd36..4515eb2 100644 --- a/views/_database.tpl +++ b/views/_database.tpl @@ -1,26 +1,32 @@ + + - {queries} - - - - + + + + + - + - {/queries} +
Time Query StringCaller
{duration}{! sql !}{trace-file}
+ +
- {trace} - {index}{file}
- {function}

- {/trace} +
diff --git a/views/_events.tpl b/views/_events.tpl index 88d732f..b472267 100644 --- a/views/_events.tpl +++ b/views/_events.tpl @@ -1,3 +1,6 @@ + @@ -7,12 +10,12 @@ - {events} + - - - + + + - {/events} +
{ duration } ms{event}{count} ms
diff --git a/views/_files.tpl b/views/_files.tpl index 9c992ab..339f3a4 100644 --- a/views/_files.tpl +++ b/views/_files.tpl @@ -1,16 +1,22 @@ + - {userFiles} + - - + + - {/userFiles} - {coreFiles} + + - - + + - {/coreFiles} +
{name}{path}
{name}{path}
diff --git a/views/_history.tpl b/views/_history.tpl index 7f22f56..ccaa712 100644 --- a/views/_history.tpl +++ b/views/_history.tpl @@ -1,28 +1,34 @@ + - - - - - - - - - + + + + + + + + + - {files} - + + + - - - - - - + + + + + - {/files} +
ActionDatetimeStatusMethodURLContent-TypeIs AJAX?
ActionDatetimeStatusMethodURLContent-TypeIs AJAX?
- + + + {datetime}{status}{method}{url}{contentType}{isAJAX}
diff --git a/views/_logs.tpl b/views/_logs.tpl index 7c80d84..ecf0d26 100644 --- a/views/_logs.tpl +++ b/views/_logs.tpl @@ -1,6 +1,11 @@ -{ if $logs == [] } -

Nothing was logged. If you were expecting logged items, ensure that LoggerConfig file has the correct threshold set.

-{ else } + + +

Nothing was logged. If you were expecting logged items, call + WizdamDebugToolbar\Collectors\Logs::addLog($level, $message) + from your application code.

+ @@ -9,12 +14,12 @@ - {logs} + - - + + - {/logs} +
{level}{msg}
-{ endif } + diff --git a/views/_routes.tpl b/views/_routes.tpl index e277046..bed70f1 100644 --- a/views/_routes.tpl +++ b/views/_routes.tpl @@ -1,35 +1,40 @@ +

Matched Route

- {matchedRoute} + - + - + - + - + - {params} + - - + + - {/params} - {/matchedRoute} + +
Directory:{directory}
Controller:{controller}
Method:{method}
Params:{paramCount} / {truePCount} /
{name}{value}
-

Defined Routes

@@ -41,12 +46,18 @@ - {routes} - - - - - - {/routes} + + + + + + + + + + +
{method}{route}{handler}
No defined routes registered.
+ +
diff --git a/views/toolbar.tpl.php b/views/toolbar.tpl.php index b86baba..bb9e44b 100644 --- a/views/toolbar.tpl.php +++ b/views/toolbar.tpl.php @@ -1,24 +1,21 @@ @@ -41,7 +38,7 @@ 🔅 - + @@ -78,14 +75,14 @@ - + - + @@ -103,21 +100,20 @@ - renderTimeline($collectors, $startTime, $segmentCount, $segmentDuration, $styles) ?> + renderTimeline($collectors, $startTime, $segmentCount, $segmentDuration, $styles) ?> - - -
-

- - setData($c['display'])->render("_{$c['titleSafe']}.tpl") ?> -
- + +
+

+ renderPartial($viewsPath, '_' . $c['titleSafe'] . '.tpl', $c['display']) ?> +
@@ -127,13 +123,10 @@ $items) : ?> -

- - $value) : ?> @@ -144,7 +137,6 @@
-

No data to display.

@@ -155,7 +147,6 @@

Session User Data

- @@ -178,100 +169,58 @@

Request ( )

- -

$_GET

-
- -
- - $value) : ?> - - - - - - -
+

$_GET

+ + $value) : ?> + + +
- -

$_POST

-
- - - - $value) : ?> - - - - - - -
+

$_POST

+ + $value) : ?> + + +
- -

Headers

-
- - - - $value) : ?> - - - - - - -
+

Headers

+ + $value) : ?> + + +
- -

Cookies

-
- - - - $value) : ?> - - - - - - - +

Cookies

+ + $value) : ?> + + +

Response ( )

- - -

Headers

-
- - - - $value) : ?> - - - - - - -
+

Headers

+ + $value) : ?> + + +

System Configuration

- - setData($config)->render('_config.tpl') ?> + renderPartial($viewsPath, '_config.tpl', $config) ?>