Extends updateStatsTrack() to collect per-hit UA breakdowns, GeoIP country, referral category, hourly distribution, and sliding session state (new/returning, depth, duration). All new data is written as additional pipe-separated fields (indices 8-16) appended to the existing statistic.log line format.
Core changes:
- UA parser (core/system.php — getAgentInfo()):
Detects browser (Chrome, Firefox, Edge, Opera, Safari, IE), OS (Windows, Android, iOS, macOS, Linux), and device (desktop, mobile, tablet, bot)
- Bot detection via UA regex; guest===1 always maps to Bot
- Counter field helpers (core/system.php):
- getCounterField(): parses "key:count,key:count" field into a map
- updateCounterField(): increments a key, keeps top-10 + Other overflow bucket
- Hourly distribution (core/system.php — updateHoursField()):
- Maintains a 24-slot comma-separated hit count per hour
- Session tracking (core/system.php — updateSessionState()):
- File-based sliding window (1800s) keyed by stats_id cookie
- Tracks first-visit, last-visit, hit depth per session
- Returns is_new, depth, duration for the current hit
- Bucket classifiers (core/system.php):
- getSessionDepthBucket(): maps depth to 1 / 2-3 / 4-7 / 8+
- getSessionDurationBucket(): maps seconds to <30s / 30s-3m / 3m-15m / 15m+
- Referral category (core/system.php — getRefCategory()):
- Classifies referer into direct, search, social, or referrer
- updateStatsTrack() extended (core/system.php):
- Gathers agent info, refcat, GeoIP country, session state before write
- Sets fields [8..16] on the statistic.log line in all three write paths
Benefits:
- Rich per-day analytics available without changing the core log file structure
- Session tracking uses httponly cookie with 1-year TTL, no DB required
- All new fields are backward-compatible (absent = treated as empty)
Technical notes:
- stats_id cookie set via setcookie() only when headers not yet sent
- Fields 8-16 extend the existing 0-7 statistic.log schema non-destructively
- Geoip::getCountry() used only if the Geoip class exists (class_exists check)
Adds four standalone HTML demo files to docs/ for the statistics subsystem: a browser analysis demo, a monitor dashboard, and two etalon/reference variants. These files are design references and are not loaded by the CMS at runtime.
Core changes:
- Demo files (docs/):
- statistic-browser-analysis-demo.html — browser/UA analysis visualization
- statistic-monitor-dashboard.html — statistics monitor dashboard layout
- statistic-monitor-etalon-demo.html — etalon reference demo
- statistic-monitor-etalon-v2.html — etalon reference v2
Benefits:
- Design references available alongside the codebase for review
- No runtime impact; files are not referenced from any CMS module
Technical notes:
- Standalone HTML; no CMS dependencies or includes
- Located in docs/ which is not served by the web root
Addresses several targeted CSS issues in the admin theme: tightens the tooltip child selector to avoid unintended matches, revises sidebar count badge styling, moves IP/flag rules outside the table context, and replaces the language-link hover background with a filter-based approach.
Core changes:
- Tooltip selector fix (theme.css):
.sl-tip div → .sl-tip > div (and ::after variant) Prevents deep descendant divs inside tooltip content from receiving position:absolute, which broke nested markup
- Sidebar count badge (theme.css):
- Count label link color: warning-strong conditional → always primary
- Removed has([data-sl-toggle-control]) color overrides on anchor
Count value span: added explicit bg-subtle background and border-strong border instead of inheriting from the danger/warning context
- Danger badge: removed background fill; uses border-strong + warning-strong text
- .sl-menu-list-image: border removed for sl_block_1 and sl_block_2 sidebar blocks
- IP / flag layout (theme.css):
Removed .sl-table-list-sort parent scope from .sl-col-ip, .sl-geo-flag, .sl-geo-flag img, .sl-col-ip a — rules now apply globally
- .sl-geo-flag: added justify-content: center
- IP link min-height and line-height: 16px → 24px (matches flag image height)
- Menu grid link (theme.css):
- Default state: removed background: bg-soft (now transparent/inherited)
- Hover: bg changed from primary-tint to bg-soft
- Language hover (theme.css):
- Removed background-color hover on .sl-admin-language-link
- Added filter: brightness(1.08) on hover for subtle brightness effect
- Set explicit background: transparent on both states
- Sort table tbody th padding (theme.css):
- Added rules for tbody th padding to match td spacing in .sl-table-list-sort
Benefits:
- Tooltip no longer bleeds position styles into nested content
- Sidebar badges visually consistent regardless of warning/normal state
- Flag + IP link correctly aligned outside table-only context
Technical notes:
- All changes are additive CSS tweaks; no HTML structure changes
- No new CSS class names introduced
Replaces raw content_html strings and ad-hoc row_attr usage in the database admin table with the typed column flags already used across other admin tables. Adds is_summary and is_no_sort flags to the table-cells and table-row fragments.
Core changes:
- Admin database module (admin/modules/database.php):
Data rows: all cells now carry typed flags (is_col_id, is_col_count, is_col_date, is_col_actions) and use has_content_text/content_text where HTML escaping is safe
Summary row: replaced bold span content_html cells with typed cells; uses is_summary to render <th> instead of <td>
- Summary row: replaced raw row_attr='data-sort-method="none"' with is_no_sort
- Table head: all columns carry typed flags; removed is_fixed from table call
- Table cells fragment (templates/admin/fragments/table-cells.html):
Added is_summary branch: when true renders <th> elements instead of <td> for the summary/totals row, preserving all class and attribute logic
- Table row fragment (templates/admin/fragments/table-row.html):
- Added is_no_sort: when true emits data-sort-method="none" on the <tr>
- Removed support for raw row_attr (no longer needed after this migration)
Benefits:
- Summary row styled as <th> for semantic correctness and proper sort exclusion
- Column widths controlled by CSS class flags, not fixed table layout
- Consistent with other admin tables; no more ad-hoc row_attr strings
Technical notes:
- is_fixed removed from database table; column sizing now via CSS col-* classes
- is_summary and is_no_sort are additive fragment flags; no other tables affected
Adds the missing getUserSessionAdminInfo case to the go=5 AJAX dispatcher in index.php and updates the update_query in getUserSessionAdminInfo() to include the correct go value and a CSRF token, preventing open-endpoint access.
Core changes:
- AJAX dispatcher (index.php):
- Added case 'getUserSessionAdminInfo': getUserSessionAdminInfo() to go=5 block
- Admin session info (core/system.php):
update_query changed from 'go=1&op=getUserSessionAdminInfo' to 'go=5&op=getUserSessionAdminInfo&token='.getSiteToken()
Benefits:
- AJAX endpoint is CSRF-protected via token parameter
- Route correctly uses the go=5 admin-AJAX block instead of go=1
Technical notes:
- getSiteToken() generates the per-session CSRF token used project-wide
- No change to the HTML output or polling behavior
Adds the GeoLite2 Country and ASN binary database files to storage/geoip/. These files are read directly by the pure-PHP MMDB parser in core/classes/geoip.php.
Core changes:
- Country database (storage/geoip/country.mmdb):
- GeoLite2-Country MMDB, ~8.9 MB
- ASN database (storage/geoip/asn.mmdb):
- GeoLite2-ASN MMDB, ~12 MB
Benefits:
- GeoIP lookups work out of the box without a separate download step
- Both country and ASN resolution available immediately after clone
Technical notes:
- Files are binary MMDB format, read via file_get_contents() and cached
- Web access blocked by storage/geoip/.htaccess (deny from all)
- Database files should be updated periodically from maxmind.com
Updates the two directory protection files in storage/geoip/ to remove trailing newlines and condense index.html to a single-line redirect. Deletes templates/admin/images/misc/sprite.png which is no longer referenced.
Core changes:
- Storage protection (storage/geoip/.htaccess):
- Removed trailing newline (content unchanged: deny from all)
- Storage index (storage/geoip/index.html):
- Condensed to single-line HTML with meta-refresh redirect to slaed.net
- Removed multi-line doctype/head/body boilerplate
- Admin image cleanup (templates/admin/images/misc/sprite.png):
- Deleted; file is no longer used after icon migration to SVG/Bootstrap-Icons
Benefits:
- Consistent with project-wide one-liner protection file convention
- Removes orphaned binary asset from admin theme
Technical notes:
- No functional changes to directory access control
- sprite.png was the last remaining PNG in templates/admin/images/misc/
Removes the legacy user_geo_ip() function from core/system.php and replaces all call sites across admin modules, frontend modules, and core files with the typed Geoip class methods (getIpHtml, getFlagHtml, getCountry, getInfo). Also removes the now-redundant getGeoipInfo() and getGeoipCountry() wrappers.
Core changes:
- System core (core/system.php):
- Removed user_geo_ip() function (flag + IP link rendering with legacy $id param)
- Removed getGeoipInfo() and getGeoipCountry() thin wrapper functions
getUserSessionInfo(): replaced user_geo_ip($host, 3) with Geoip::getFlagHtml()
- 2 call sites updated (registered user, bot)
- getUserSessionAdminInfo(): replaced 5 call sites with Geoip::getFlagHtml()
- ashowcom(): replaced user_geo_ip($com_host, 4) with Geoip::getIpHtml()
- Core user (core/user.php):
- getPrivateMessageView(): user_geo_ip($ip_sender, 4) → Geoip::getIpHtml()
- Frontend entry (index.php):
Language auto-detection: replaced user_geo_ip(getIp(), 2) with Geoip::getCountry() using ISO 2-letter code lookup table
- Removed string-matching against country names
- Added compact array map: ['GB'=>'en','US'=>'en','CA'=>'en','AU'=>'en',
'FR'=>'fr','DE'=>'de','PL'=>'pl','RU'=>'ru','UA'=>'uk']
- Admin modules (comments, security, config):
- comments.php: 2 call sites → Geoip::getIpHtml()
- security.php: 1 call site → Geoip::getIpHtml()
- config.php: getGeoipRows() calls Geoip::getInfo() directly
- Frontend/admin modules (13 files):
account/admin/index.php, account/index.php, auto_links/admin/index.php, faq/admin/index.php, files/admin/index.php, forum/index.php, help/admin/index.php, jokes/admin/index.php, links/admin/index.php, media/admin/index.php, money/admin/index.php, news/admin/index.php, order/admin/index.php, pages/admin/index.php, users/index.php, whois/admin/index.php
- All user_geo_ip($ip, 4) → Geoip::getIpHtml($ip)
- account/index.php: user_geo_ip($ip, 2) → Geoip::getInfo() with country_name
Benefits:
- user_geo_ip() fully removed; all IP display goes through the Geoip class
- ISO code-based language detection is cleaner and extensible
- No functional regression: output HTML shape unchanged
Technical notes:
user_geo_ip() $id param modes (1=ip, 2=country_name, 3=flag, 4=flag+link) replaced by explicit Geoip method names
- getip() renamed to getIp() in index.php call (case correction)
- All replacements are one-to-one; no logic change in surrounding code
Removes the separate config/geoip.php file and merges GeoIP settings directly into config/global.php as flat top-level keys (geoip_anon, geoip_asn, geoip_cache, geoip_country, geoip_enabled, geoip_store, geoip_test). Aligns config schema with the Geoip class refactor.
Core changes:
- Config migration (config/global.php):
- Removed legacy geo_ip key
- Added flat geoip_* keys replacing nested geoip.php structure
- Config deletion (config/geoip.php):
- File removed; settings now live in global.php
- Config fingerprint (config/local.php):
- Updated base_fingerprint after schema change
- Admin config panel (admin/modules/config.php):
- getGeoipPanel(): reads $conf['geoip_*'] flat keys instead of $conf['geoip'][...]
- getGeoipRows(): calls Geoip::getInfo() directly; removed getGeoipInfo() wrapper
- Removed checkPerms(CONFIG_DIR.'/geoip.php') call from panel output
- save(): GeoIP settings now written into global.php via existing setConfigFile()
- Removed separate setConfigFile('geoip.php', ...) block
Redirect simplified: testip param removed from redirect; test IP persisted in geoip_test key of global.php instead
Benefits:
- Single config file for all settings; no extra file to manage
- Consistent with existing global.php admin save pattern
Technical notes:
- Old geo_ip boolean key removed; replaced by geoip_enabled int key
- setConfigFile('geoip.php') call and geoip.php file both eliminated
- No URL or DB schema changes
Removes the geoip2/geoip2 Composer dependency and replaces the MaxMind PHP SDK reader with a self-contained pure-PHP MMDB binary parser inside the Geoip class. All lookup results and public API shape remain unchanged.
Core changes:
- Geoip class (core/classes/geoip.php):
- Removed getReader() which required GeoIp2\Database\Reader
- Added getMmdb(): loads and caches the raw MMDB binary payload + metadata
- Added getRecord(): walks the radix search tree to locate a data record
Added getMmdbNode(), getMmdbDecode(), getMmdbValue(), getMmdbSize(), getMmdbPointer(), getMmdbUint(): pure-PHP MMDB binary format decoder
- Extracted getCountryData() and getAsnData() private helpers with own caches
- Config keys changed from nested $conf['geoip'][...] to flat $conf['geoip_*']
- Added getFlagHtml(): returns flag <span> HTML for a given IP
- Added getIpHtml(): returns flag + IP link HTML for admin display
- getCountry(): now calls getCountryData() directly; avoids full getInfo() cost
- Composer (composer.json):
- Removed geoip2/geoip2 ^3.1 requirement
Benefits:
- Zero external runtime dependency for GeoIP lookups
- Database files read with file_get_contents() and cached in static property
- Fully self-contained; works without Composer autoload for GeoIP
Technical notes:
- Supports MMDB record sizes 24, 28, and 32 bits
- IPv4-in-IPv6 mapping handled via 96-bit prefix traversal
- Backward compatible: getInfo(), getCountry(), getEmpty(), getFileInfo() unchanged