Selamlar,
dev sitede denedim çalışmadı, cursor ile değerlendirdiğimde aşağıdaki gibi bir rapor sundu.
Bilginize.
# iyzico Installment for WooCommerce Plugin Review Status Report
**Plugin:** iyzico Installment (iyzico-installment)
**Version reviewed:** 1.1.0
**Review date:** February 21, 2025
---
## Executive Summary
| Metric | Value |
|--------|--------|
| **Overall Health Score** | **68/100** |
| **Critical Issues** | 3 |
| **High Priority** | 5 |
| **Medium Priority** | 6 |
| **Low Priority** | 4 |
The plugin is functionally structured and follows many WordPress/WooCommerce practices (nonces, capability checks, sanitization, escaping, Options API, transients, HPOS declaration). Critical and high-priority issues center on missing dependency checks, broken/inactive features (logger, custom CSS, frontend styles), and one security/logic bug in the logger. Addressing these will significantly improve stability, security, and user experience.
---
## Detailed Findings
### 1. PERFORMANCE
**Status: ⚠️ Warning**
#### Issues
| # | File | Line(s) | Severity | Description | Impact | Recommended fix |
|---|------|---------|----------|-------------|--------|-----------------|
| 1 | `includes/class-iyzico-installment-dynamic.php` | 101241 | Medium | Large inline script in `wp_footer` (140+ lines) on every product page when dynamic installments are enabled | Unnecessary HTML size, no caching of script, harder to maintain | Move JS to a separate file; enqueue with `wp_enqueue_script` and pass data via `wp_localize_script` |
| 2 | `includes/class-iyzico-installment-dynamic.php` | 314323 | Low | No caching of API installment response per price/product | Repeated identical requests for same price cause repeated API calls | Cache `getInstallmentInfo()` result in a transient keyed by price (e.g. `iyzico_installment_{price}_{bin}`) with short TTL (e.g. 515 min) |
| 3 | `includes/class-iyzico-installment-settings.php` | 66, 94, 107 | Low | `get_option()` / `update_option()` on every settings read/write | Minor; options are single key | Acceptable; consider single cached property in request if read many times |
| 4 | `includes/class-iyzico-installment-frontend.php` | 6192, 174198 | Medium | Script and localize data enqueued twice when shortcode is used (once in `enqueueScripts`, once in `renderShortcode`) | Duplicate script/localize on shortcode pages | Enqueue only once; e.g. set a static flag or check `wp_script_is('iyzico-installment', 'enqueued')` before enqueuing in shortcode |
#### Best practices followed
- Conditional script/style loading (admin only on settings page; frontend when product/shortcode).
- Transients used for API status and rate limiting.
- No N+1 or custom SQL; no database schema.
- `nocache_headers()` in AJAX response for dynamic installment (appropriate for dynamic content).
#### Recommendations
- Add transient caching for installment API responses by price (and BIN if used).
- Move dynamic installment inline script to a dedicated JS file and use `wp_localize_script`.
- Avoid double enqueue of the same script when shortcode is used.
---
### 2. SECURITY
**Status: ✅ Pass (with minor notes)**
#### Issues
| # | File | Line(s) | Severity | Description | Impact | Recommended fix |
|---|------|---------|----------|-------------|--------|-----------------|
| 1 | `includes/class-iyzico-installment-dynamic.php` | 334 | Low | Rate limit uses `$_SERVER['REMOTE_ADDR']` | Can be spoofed via headers (e.g. X-Forwarded-For) on some hosts | Prefer `wp_get_remote_address()` (WP 6.7+) or a helper that respects proxy headers consistently |
| 2 | `includes/admin/class-iyzico-installment-admin.php` | 296307 | Low | API key and secret key printed in HTML `value=""` | Risk if admin pages are cached or logged | Avoid echoing raw secrets; show masked value and Change flow, or load via JS for test-only |
| 3 | `includes/class-iyzico-installment-dynamic.php` | 364419 | Low | Regex in `_sanitizeCss()` may be too strict and block valid CSS (e.g. `@`, backslash) | Some valid custom CSS might be rejected | Use a safer allowlist (e.g. `wp_strip_all_tags` + blocklist of `javascript:`, `expression(`, etc.) or a dedicated CSS sanitizer |
#### Best practices followed
- **Nonces:** All AJAX handlers verify nonce (`iyzico_installment_nonce`, `installment_nonce`).
- **Capabilities:** Admin AJAX (`ajaxTestApi`) checks `current_user_can('manage_options')`.
- **Input:** `sanitize_text_field`, `floatval`, `intval`, `wp_unslash` used on `$_POST`/input.
- **Output:** `esc_html`, `esc_attr`, `esc_url`, `esc_js`, `esc_textarea`, `wp_kses_post`, `wp_json_encode` used appropriately.
- **Custom CSS:** Admin and dynamic/frontend paths sanitize CSS (strip tags, blocklist dangerous patterns).
- **SQL:** No raw queries; no `$wpdb->prepare` needed (no custom DB).
- **Rate limiting:** Present for public `get_installment_options` AJAX.
- **Credentials:** Stored in options (not in code); API test uses POST credentials.
#### Recommendations
- Use `wp_get_remote_address()` (or equivalent) for rate limiting when on WP 6.7+.
- Avoid echoing API secret in form `value`; use masked display and separate Test connection flow.
---
### 3. WOOCOMMERCE COMPATIBILITY
**Status: ⚠️ Warning**
#### Issues
| # | File | Line(s) | Severity | Description | Impact | Recommended fix |
|---|------|---------|----------|-------------|--------|-----------------|
| 1 | `iyzico-installment.php` | | **Critical** | No runtime check for WooCommerce (or iyzico-woocommerce) before loading | If plugin is active without WooCommerce, calls to `wc_get_product()`, `wc_price()`, `get_woocommerce_currency_symbol()`, `woocommerce_product_tabs` can cause fatals or broken behavior | Add `plugins_loaded` (or similar) check: `if ( ! class_exists( 'WooCommerce' ) ) { add_action( 'admin_notices', ... ); return; }` and only then require plugin files |
| 2 | `iyzico-installment.php` | | High | No minimum WooCommerce version check (header says WC requires at least: 9.3.3) | Incompatible WC versions may cause subtle bugs | Check `defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '9.3.3', '>=' )` and show notice / bail if not met |
| 3 | `includes/class-iyzico-installment-frontend.php` | 109115 | Low | Uses `has_shortcode( $post->post_content, 'iyzico_installment' )` for script loading | Works for simple cases; may miss shortcodes in blocks or other contexts | Consider also checking for `dynamic_iyzico_installment` and block content if needed |
| 4 | `includes/class-iyzico-installment-hpos.php` | 1617 | Low | Inconsistent spacing in `if (! defined('ABSPATH') )` | Style only | Use `if ( ! defined( 'ABSPATH' ) )` for consistency with rest of codebase |
#### Best practices followed
- **HPOS:** Declares compatibility with `custom_order_tables` via `FeaturesUtil::declare_compatibility` in `before_woocommerce_init`.
- **Hooks:** Uses `woocommerce_product_tabs` for product tab integration.
- **APIs:** Uses `wc_get_product()`, `wc_price()`, `get_woocommerce_currency_symbol()`.
- **Logging:** Uses `wc_get_logger()` where needed (with `function_exists` check).
- **Product:** Uses global `$product` and `WC_Product` checks on product pages.
#### Recommendations
- Add WooCommerce (and optionally iyzico-woocommerce) presence and minimum version check at bootstrap; display admin notice and do not load plugin logic if not met.
- Keep HPOS declaration and WooCommerce API usage as-is.
---
### 4. WORDPRESS BEST PRACTICES
**Status: ⚠️ Warning**
#### Issues
| # | File | Line(s) | Severity | Description | Impact | Recommended fix |
|---|------|---------|----------|-------------|--------|-----------------|
| 1 | `includes/class-iyzico-installment-frontend.php` | 389413, 427 | **Critical** | Custom CSS is added with `wp_add_inline_style( 'iyzico-installment', $custom_css )` but no style with handle `iyzico-installment` is ever enqueued (that handle is used for the script) | Admin-defined custom CSS never appears on shortcode/tab frontend | Enqueue a (possibly empty) stylesheet first, e.g. `wp_enqueue_style( 'iyzico-installment', ... )` or a null style, then `wp_add_inline_style( 'iyzico-installment', $custom_css )`; or use a dedicated handle like `iyzico-installment-inline` and enqueue a dummy dependency |
| 2 | `iyzico-installment.php` + `includes/class-iyzico-installment-frontend.php` | | **High** | Root `style.css` (plugin styles for `.iyzico-installment-container`, tables, etc.) is never enqueued | Frontend installment blocks may appear unstyled | In `Iyzico_Installment_Frontend::enqueueScripts()` (and when shortcode enqueues assets), add `wp_enqueue_style( 'iyzico-installment-frontend', IYZI_INSTALLMENT_URL . 'style.css', array(), IYZI_INSTALLMENT_VERSION )` (or equivalent path) |
| 3 | `iyzico-installment.php` | 46 | High | `class-iyzico-installment-logger.php` is required but the class is never instantiated | Dead code; logger not used anywhere | Either remove the require and the logger class file, or instantiate and use the logger for API/errors and fix the loggers append logic (see Code Quality) |
| 4 | `includes/admin/class-iyzico-installment-admin.php` | 110112, 128130 | Low | Duplicate handling of `enable_dynamic_installments` in `sanitizeSettings()` | Redundant; second block overwrites first | Remove the duplicate block (lines 128130) |
| 5 | `includes/class-iyzico-installment-frontend.php` | 427 | | Inline style attached to script handle | See issue 1 above | Use a proper style handle and enqueue a style first |
| 6 | i18n | Multiple | Low | Many strings use ALL_CAPS keys (e.g. `IYZICO_INSTALLMENT_OPTIONS`) | Usable with .po/.mo; not human-readable in code | Acceptable; ensure all user-facing strings are in .po files |
#### Best practices followed
- **ABSPATH:** All PHP files guard with `if ( ! defined( 'ABSPATH' ) ) exit;`
- **Options API:** Single option key `iyzico_installment_settings` with `get_option` / `update_option` / `register_setting` and sanitize callback
- **Assets:** `wp_enqueue_script` / `wp_enqueue_style` with version and dependencies; admin scripts only on settings page
- **i18n:** Text domain `iyzico-installment`, Domain Path set; `__()`, `esc_html__()`, `esc_attr__()`, etc. used; .po files present
- **Hooks:** Actions/filters used for menus, settings, shortcodes, tabs, AJAX
- **No direct DB:** No custom tables or `$wpdb` usage
- **AJAX:** Uses `admin-ajax.php`, nonce, and appropriate handlers for logged-in and nopriv
#### Recommendations
- Enqueue frontend `style.css` and a style handle for custom CSS so both default and custom CSS apply.
- Either remove or properly integrate the logger and fix its append behavior.
- Remove duplicate `enable_dynamic_installments` sanitization.
---
### 5. CODE QUALITY
**Status: ⚠️ Warning**
#### Issues
| # | File | Line(s) | Severity | Description | Impact | Recommended fix |
|---|------|---------|----------|-------------|--------|-----------------|
| 1 | `includes/class-iyzico-installment-logger.php` | 5674 | **Critical** | `put_contents()` overwrites the log file; the `else` branch then reads the file (already overwritten) and writes `existing_content . $log_message`, so the first write loses previous lines and the second write duplicates the current line | Log file often contains only the latest message or duplicated lines; logging unreliable | Always read existing content first (if file exists), then write `existing_content . $log_message` in one `put_contents` call; or use a single append path (e.g. `get_contents` + `put_contents( $path, $existing . $log_message )`) |
| 2 | `includes/class-iyzico-installment-dynamic.php` | 356 | Medium | Rate limit uses `( $requests + 1 )` when `$requests` can be `false` (first request); in PHP `false + 1 = 1` | Works by accident | Explicit: `$count = is_numeric( $requests ) ? (int) $requests + 1 : 1; set_transient( $transient_key, $count, 60 );` |
| 3 | `includes/class-iyzico-installment-settings.php` | 5157 | Low | If `get_option()` returns non-array (e.g. corrupted), `update_option( ..., $this->_defaults )` could reset settings | Edge case | Use `$settings = get_option( self::OPTION_KEY, $this->_defaults );` and if not is_array, set to `$this->_defaults` before update |
| 4 | Multiple | | Low | Inconsistent formatting (e.g. `class-iyzico-installment-hpos.php` uses `if (! defined(...)` ) | Readability | Apply WordPress PHP coding standards consistently (spaces after `if`, etc.) |
| 5 | `assets/js/iyzico-installment.js` | 325 | Low | Table header text hardcoded in JS: `'Taksit Sayısı'`, `'Taksit Tutarı'`, `'Toplam'` | Not translatable when table is rendered client-side | Pass translated strings via `wp_localize_script` and use them in `renderInstallmentTable` (or keep server-rendered table and avoid duplicating strings in JS) |
| 6 | `includes/class-iyzico-installment-dynamic.php` | 398410 | Low | `_sanitizeCss()` regex allows limited character set; may block valid CSS (e.g. `@font-face`, `url()`) | Custom CSS might be stripped | Broaden allowlist or rely on blocklist of dangerous tokens only |
#### Best practices followed
- **Structure:** Clear class split (Settings, API, Frontend, Admin, Dynamic, HPOS).
- **PHPDoc:** Classes and many methods documented.
- **Dependency injection:** Settings and API passed into Frontend/Dynamic/Admin.
- **Type use:** Use of `floatval`, `intval`, and WC APIs keeps types predictable.
- **DRY:** Installment table and bank logo logic exist in two places (Frontend + Dynamic) but are consistent; could be centralized in a small helper in a follow-up refactor.
#### Recommendations
- Fix logger to true append (read then write in one go).
- Make rate limit counter handling explicit for first request.
- Pass table header strings from PHP to JS for translation when rendering via JS.
- Align formatting with WordPress PHP coding standards.
---
## Priority Action Items
### Critical
1. **WooCommerce dependency (iyzico-installment.php)**
Before loading any includes, check `class_exists( 'WooCommerce' )` (and optionally iyzico-woocommerce). If not present, register an `admin_notices` callback and `return` so plugin code does not run.
2. **Custom CSS not loading (includes/class-iyzico-installment-frontend.php)**
Enqueue a stylesheet with handle `iyzico-installment` (or a dedicated one for inline), then call `wp_add_inline_style()` with that same handle so admin custom CSS is output on the frontend.
3. **Logger append bug (includes/class-iyzico-installment-logger.php)**
Replace the current write logic with: read existing content if file exists, then write `existing_content . $log_message` once. If the logger is not used, remove the require and file instead.
### High
4. **Frontend style.css never enqueued (includes/class-iyzico-installment-frontend.php)**
In `enqueueScripts()` (and wherever shortcode enqueues assets), add `wp_enqueue_style( 'iyzico-installment-frontend', IYZI_INSTALLMENT_URL . 'style.css', array(), IYZI_INSTALLMENT_VERSION );` (adjust constant if you use a different base URL for assets).
5. **WooCommerce version check (iyzico-installment.php)**
After checking WooCommerce exists, add `version_compare( WC_VERSION, '9.3.3', '>=' )`; if false, show notice and do not load plugin logic.
6. **Logger unused or integrated (iyzico-installment.php)**
Either remove `require_once ... class-iyzico-installment-logger.php` and the logger file, or instantiate the logger and use it for API/error logging (after fixing the append bug).
7. **Double enqueue on shortcode (includes/class-iyzico-installment-frontend.php)**
In `renderShortcode()`, avoid calling `wp_enqueue_script` and `wp_localize_script` again if the script is already enqueued (e.g. check `wp_script_is( 'iyzico-installment', 'enqueued' )`), or centralize enqueue in one place.
8. **Admin credentials in HTML (includes/admin/class-iyzico-installment-admin.php)**
Do not echo secret key (and preferably not API key) in plain text in form values; use masked display and Test connection with POST body only.
### Medium
9. **Inline script in dynamic (includes/class-iyzico-installment-dynamic.php)**
Move the footer script to a separate JS file and enqueue it; pass `ajax_url`, `nonce`, `product_id`, prices, etc. via `wp_localize_script`.
10. **API response caching (includes/class-iyzico-installment-api.php or class-iyzico-installment-dynamic.php)**
Cache `getInstallmentInfo()` result in a transient (key: e.g. price + BIN) with TTL 515 minutes to reduce external API calls.
11. **Rate limit counter (includes/class-iyzico-installment-dynamic.php)**
Set transient value explicitly for first request: e.g. `$count = ( $requests !== false && is_numeric( $requests ) ) ? (int) $requests + 1 : 1; set_transient( $transient_key, $count, 60 );`.
12. **Duplicate sanitization (includes/admin/class-iyzico-installment-admin.php)**
Remove the second `enable_dynamic_installments` block (lines 128130) in `sanitizeSettings()`.
13. **CSS sanitization regex (includes/class-iyzico-installment-dynamic.php)**
Loosen or replace the strict regex in `_sanitizeCss()` so valid CSS (e.g. `@font-face`, `url()`) is not stripped; keep blocklist of dangerous patterns.
14. **Remote address for rate limit (includes/class-iyzico-installment-dynamic.php)**
Use `wp_get_remote_address()` if WordPress version supports it for more reliable IP for rate limiting.
---
## Code Examples
### 1. Logger append fix (Critical)
**File:** `includes/class-iyzico-installment-logger.php`
**Current (buggy):**
```php
if ( ! $wp_filesystem->put_contents( $this->_logFile, $log_message, FS_CHMOD_FILE ) ) {
// fallback...
} else {
if ( $wp_filesystem->exists( $this->_logFile ) ) {
$existing_content = $wp_filesystem->get_contents( $this->_logFile );
$wp_filesystem->put_contents( $this->_logFile, $existing_content . $log_message, FS_CHMOD_FILE );
} else {
$wp_filesystem->put_contents( $this->_logFile, $log_message, FS_CHMOD_FILE );
}
}
```
**Recommended:** Always append by reading first, then writing once:
```php
$existing_content = '';
if ( $wp_filesystem->exists( $this->_logFile ) ) {
$existing_content = $wp_filesystem->get_contents( $this->_logFile );
}
$new_content = $existing_content . $log_message;
if ( ! $wp_filesystem->put_contents( $this->_logFile, $new_content, FS_CHMOD_FILE ) ) {
$file_put_contents_result = file_put_contents( $this->_logFile, $log_message, FILE_APPEND );
if ( false === $file_put_contents_result ) {
error_log( "Iyzico Logger: Failed to write to log file: {$this->_logFile}" );
}
}
```
### 2. Enqueue frontend style and custom CSS (Critical / High)
**File:** `includes/class-iyzico-installment-frontend.php`
**Add in `enqueueScripts()` after script enqueue:**
```php
wp_enqueue_style(
'iyzico-installment-frontend',
IYZI_INSTALLMENT_URL . 'style.css',
array(),
IYZI_INSTALLMENT_VERSION
);
```
**Fix custom CSS** by using an enqueued style handle for inline CSS:
```php
// Ensure a style is enqueued so inline CSS is output
wp_enqueue_style(
'iyzico-installment-inline',
IYZI_INSTALLMENT_ASSETS_URL . '/css/iyzico-installment-inline.css', // or a minimal empty file / inline dependency
array(),
IYZI_INSTALLMENT_VERSION
);
wp_add_inline_style( 'iyzico-installment-inline', $custom_css );
```
(Alternatively, enqueue the same `iyzico-installment-frontend` handle and use `wp_add_inline_style( 'iyzico-installment-frontend', $custom_css );` so one stylesheet carries both base and custom CSS.)
### 3. WooCommerce dependency check (Critical)
**File:** `iyzico-installment.php`
**Add after constants, before any `require_once`:**
```php
add_action( 'plugins_loaded', function () {
if ( ! class_exists( 'WooCommerce' ) ) {
add_action( 'admin_notices', function () {
echo '<div class="notice notice-error"><p>';
esc_html_e( 'iyzico Installment requires WooCommerce to be installed and active.', 'iyzico-installment' );
echo '</p></div>';
} );
return;
}
if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '9.3.3', '<' ) ) {
add_action( 'admin_notices', function () {
echo '<div class="notice notice-warning"><p>';
esc_html_e( 'iyzico Installment requires WooCommerce 9.3.3 or newer.', 'iyzico-installment' );
echo '</p></div>';
} );
return;
}
// Move current require_once and instantiation here (inside the callback)
}, 5 );
```
Then move all current `require_once` and `$GLOBALS[...]` assignments into that callback so they run only when WooCommerce is present and version is sufficient.
---
## Compatibility Matrix
| Component | Version / note |
|-----------|-----------------|
| **WordPress** | 6.6+ (header: Requires at least 6.6; Tested up to 6.8) |
| **WooCommerce** | 9.3.3+ (header: WC requires at least 9.3.3; WC tested up to 9.7.1) |
| **PHP** | 7.4.33+ |
| **Plugin conflicts** | Declares dependency on `woocommerce` and `iyzico-woocommerce` in header; no runtime conflict checks found. No custom tables; HPOS declared compatible. |
---
## Performance Metrics
| Metric | Estimate / assessment |
|--------|------------------------|
| **Database queries per page load** | Low. Options: 12 `get_option` (settings), optional 1 `get_transient` (API status / rate limit). No custom tables or heavy queries. |
| **Memory usage** | Low. No large data structures; API responses are bounded. |
| **Cache compatibility** | Transients used for API status and rate limit. No full-page cache bypass for installment content; dynamic installment content is appropriate to be non-cached. Consider cache-friendly design if you ever serve static installment blocks. |
| **External API** | One iyzico API call per installment request; no response caching currently (recommended to add transient cache by price). |
---
## Summary Table by Category
| Category | Status | Critical | High | Medium | Low |
|----------|--------|----------|------|--------|-----|
| Performance | ⚠️ | 0 | 0 | 2 | 2 |
| Security | ✅ | 0 | 0 | 0 | 3 |
| WooCommerce | ⚠️ | 1 | 1 | 0 | 2 |
| WordPress | ⚠️ | 2 | 2 | 0 | 2 |
| Code quality | ⚠️ | 1 | 0 | 1 | 4 |
| **Total** | | **3** | **5** | **6** | **4** |
---
*End of report.*