therealworld / tools-plugin
Shared foundation for the-real-world OXID 7 modules: helpers for config, DB, files, (S)FTP, APIs, XML/CSV and strings, Monolog logging, Twig filters and contracts for page cache and form protection.
Package info
bitbucket.org/therealworld/tools-plugin
Type:oxideshop-component
pkg:composer/therealworld/tools-plugin
Requires
- php: ^8.3
- ext-curl: *
- ext-iconv: *
- ext-json: *
- ext-simplexml: *
- ext-xmlreader: *
- ext-xmlwriter: *
- ext-zlib: *
- fale/isbn: >=v3.1
- guzzlehttp/guzzle: ^7
- html2text/html2text: >=v4.3
- mobiledetect/mobiledetectlib: >=v2.8
- oxid-esales/oxideshop-ce: >=v7.4
- viison/address-splitter: ^0.3
Requires (Dev)
None
Suggests
- ext-ftp: Needed by ToolsFTP for FTP/FTPS transfers, guarded at runtime via ToolsFTP::isConnectionAvailable(). A shop that moves no files by FTP does not need it, and hard-requiring it blocks composer install on lean PHP builds.
- ext-posix: Used by the CommandOwnershipGuard trait to warn when a console command writes into a directory owned by another user (e.g. the web server), guarded at runtime via function_exists('posix_geteuid'). Without it the warning is simply skipped.
- ext-soap: Needed by ToolsApi::executeExternApiSoap() for SOAP endpoints, guarded at runtime via ToolsApi::isSoapAvailable().
- ext-zip: Needed by ToolsFile::compressZipFile() and extractZipFile(), guarded at runtime via ToolsFile::isZipAvailable().
- phpseclib/phpseclib: Version 4 or newer, needed by ToolsFTP::uploadFileSFTP() for SFTP transfers, guarded at runtime via ToolsFTP::isSftpAvailable(). Optional on purpose: phpseclib installs under a versioned namespace (phpseclib4\ against phpseclib3\) but as one package name, so a hard requirement here decides the version for the whole shop - and third-party code that still writes phpseclib3\ then breaks at runtime rather than at install time. The module that actually transfers by SFTP requires it; a shop that does not need it at all.
Provides
None
Conflicts
None
Replaces
None
- v3.x-dev
- v3.10.0
- v3.9.0
- v3.8.0
- v3.7.5
- v3.7.4
- v3.7.3
- v3.7.2
- v3.7.1
- v3.7.0
- v3.6.6
- v3.6.5
- v3.6.4
- v3.6.3
- v3.6.2
- v3.6.1
- v3.6.0
- v3.5.8
- v3.5.7
- v3.5.6
- v3.5.5
- v3.5.4
- v3.5.3
- v3.5.2
- v3.5.1
- v3.5.0
- v3.4.2
- v3.4.1
- v3.4.0
- v3.3.14
- v3.3.13
- v3.3.12
- v3.3.11
- v3.3.10
- v3.3.9
- v3.3.8
- v3.3.7
- v3.3.6
- v3.3.5
- v3.3.4
- v3.3.3
- v3.3.2
- v3.3.1
- v3.3.0
- v3.2.12
- v3.2.11
- v3.2.10
- v3.2.9
- v3.2.8
- v3.2.7
- v3.2.6
- v3.2.5
- v3.2.4
- v3.2.3
- v3.2.2
- v3.2.1
- v3.2.0
- v3.1.3
- v3.1.2
- v3.1.1
- v3.1.0
- v3.0.69
- v3.0.68
- v3.0.67
- v3.0.66
- v3.0.65
- v3.0.64
- v3.0.63
- v3.0.62
- v3.0.61
- v3.0.60
- v3.0.59
- v3.0.58
- v3.0.57
- v3.0.56
- v3.0.55
- v3.0.54
- v3.0.53
- v3.0.52
- v3.0.51
- v3.0.50
- v3.0.49
- v3.0.48
- v3.0.47
- v3.0.46
- v3.0.45
- v3.0.44
- v3.0.43
- v3.0.42
- v3.0.41
- v3.0.40
- v3.0.39
- v3.0.38
- v3.0.37
- v3.0.36
- v3.0.35
- v3.0.34
- v3.0.33
- v3.0.32
- v3.0.31
- v3.0.30
- v3.0.29
- v3.0.28
- v3.0.27
- v3.0.26
- v3.0.25
- v3.0.24
- v3.0.23
- v3.0.22
- v3.0.21
- v3.0.20
- v3.0.19
- v3.0.18
- v3.0.17
- v3.0.16
- v3.0.15
- v3.0.14
- v3.0.13
- v3.0.12
- v3.0.11
- v3.0.10
- v3.0.9
- v3.0.8
- v3.0.7
- v3.0.6
- v3.0.5
- v3.0.4
- v3.0.3
- v3.0.2
- v3.0.1
- v3.0.0
- v2.3.25
- v2.3.24
- v2.3.23
- v2.3.22
- v2.3.21
- v2.3.20
- v2.3.19
- v2.3.18
- v2.3.17
- v2.3.16
- v2.3.15
- v2.3.14
- v2.3.13
- v2.3.12
- v2.3.11
- v2.3.10
- v2.3.9
- v2.3.8
- v2.3.7
- v2.3.6
- v2.3.5
- v2.3.4
- v2.3.3
- v2.3.2
- v2.3.1
- v2.3.0
- v2.2.37
- v2.2.36
- v2.2.35
- v2.2.34
- v2.2.33
- v2.2.32
- v2.2.31
- v2.2.30
- v2.2.29
- v2.2.28
- v2.2.27
- v2.2.26
- v2.2.25
- v2.2.24
- v2.2.23
- v2.2.22
- v2.2.21
- v2.2.20
- v2.2.19
- v2.2.18
- v2.2.17
- v2.2.16
- v2.2.15
- v2.2.14
- v2.2.13
- v2.2.12
- v2.2.11
- v2.2.10
- v2.2.9
- v2.2.8
- v2.2.7
- v2.2.6
- v2.2.5
- v2.2.4
- v2.2.3
- v2.2.2
- v2.2.1
- v2.2.0
- v2.1.38
- v2.1.37
- v2.1.36
- v2.1.35
- v2.1.34
- v2.1.33
- v2.1.32
- v2.1.31
- v2.1.30
- v2.1.29
- v2.1.28
- v2.1.27
- v2.1.26
- v2.1.25
- v2.1.24
- v2.1.23
- v2.1.22
- v2.1.21
- v2.1.20
- v2.1.19
- v2.1.18
- v2.1.17
- v2.1.16
- v2.1.15
- v2.1.14
- v2.1.13
- v2.1.12
- v2.1.11
- v2.1.10
- v2.1.9
- v2.1.8
- v2.1.7
- v2.1.6
- v2.1.5
- v2.1.4
- v2.1.3
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.16
- v2.0.15
- v2.0.14
- v2.0.13
- v2.0.12
- v2.0.11
- v2.0.10
- v2.0.9
- v2.0.8
- v2.0.7
- v2.0.6
- v2.0.5
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
- dev-master
- dev-oxid61
This package is auto-updated.
Last update: 2026-10-27 12:52:55 UTC
README
Deutsche Fassung: README.de.md

description
Shared foundation of all the-real-world OXID 7 modules: helpers for config, database, files, (S)FTP, HTTP APIs, XML/CSV and strings, Monolog logging, typed access to request, session, cookie and model data, Twig filters, and contracts (interfaces with no-op defaults) that let modules work together without depending on each other - page cache, form protection, shipping data, return policy, visitor consent.
- composer package:
therealworld/tools-plugin - composer type:
oxideshop-component- loaded by composer, no OXID module: there is nometadata.php, nothing to activate, and itsservices.yamlis always loaded. - namespace:
TheRealWorld\ToolsPlugin\
For the shop operator there is nothing to configure: the package is installed automatically as a dependency of the modules that need it. The rest of this document is aimed at developers.
requirements and installation
- OXID eShop CE
>= 7.4 - PHP
^8.3withext-json,ext-curl,ext-zlib,ext-iconv,ext-simplexml,ext-xmlreader,ext-xmlwriter - libraries:
guzzlehttp/guzzle^7,fale/isbn,html2text/html2text,mobiledetect/mobiledetectlib,viison/address-splitter
composer require therealworld/tools-plugin
Optional - only needed for the respective feature and checked at runtime (composer.json
suggest):
| Extension / package | Needed by | Runtime check |
|---|---|---|
ext-ftp | ToolsFTP (FTP/FTPS) | ToolsFTP::isConnectionAvailable() |
ext-soap | ToolsApi::executeExternApiSoap() | ToolsApi::isSoapAvailable() |
ext-zip | ToolsFile::compressZipFile() / extractZipFile() | ToolsFile::isZipAvailable() |
ext-posix | CommandOwnershipGuard (owner warning in console commands) | function_exists('posix_geteuid'); without it the warning is skipped |
phpseclib/phpseclib >= 4 | ToolsFTP::uploadFileSFTP() | ToolsFTP::isSftpAvailable() |
phpseclib is optional on purpose: version 4 installs under the namespace phpseclib4\ but with
the same package name, so a hard requirement here would decide the version for the whole shop.
The module that actually transfers by SFTP requires it.
conventions
These rules apply to every module building on this library.
- DB result keys are always lowercase.
ToolsDB::fetchAllByQb()andToolsDB::normaliseRows()(forgetCp()->fetchAllAssociative()) return the column keys in lowercase, whatever theSELECTsays. MySQL itself answers in mixed case: base tables as typed in theSELECT,oxv_*views andinformation_schemaalways uppercase. So: write columns lowercase in theSELECT, read$aRow['oxtitle'], and never read a raw$qb->execute()->fetchAssociative()without normalising. Never setPDO::ATTR_CASEon the shared connection. (For writing - INSERT/UPDATE/batchReplace()/batchUpsert()- the case does not matter.) - Typed access instead of
mixed:- model fields:
DataGetter(getTRWStringData(),getTRWIntData(),getTRWFloatData(),getTRWBoolData(),getTRWDateData()...) instead ofgetFieldData() - request / session / cookies:
RequestGetter,SessionGetter,CookieGetter(and theirStatic*variants) - single untyped values (
getConfigParam(),getViewDataElement(), raw rows):ToolsCast - structures we do not control (API answers, webhooks, decoded JSON/XML, import payloads):
ToolsArray::arrayCheck*()for arrays,ToolsProp::propCheck*()for objects - two levels via the optional sub-key, deeper by chainingarrayCheckArray()/propCheckObject(). The first argument is deliberatelymixed, so the calls chain withoutis_array()ladders.
- model fields:
- Module settings from static config classes:
StaticModuleSettings(+StaticModuleDebugLog) instead of per-module getter boilerplate; the class only implementsgetModuleId(). - Services: new code uses constructor injection,
$this->getService()(classes extendingCore\Base) orContainerFacade::get(). TheServiceContainer/StaticServiceContainertraits only wrapContainerFacadeand are kept for existing call sites. - Twig filters/functions live here, not in a module: an unknown filter aborts the whole template, and only this package is guaranteed to be present.
overview: core classes (src/Core/)
Almost all of them are static helper classes.
| Class | Purpose |
|---|---|
ToolsDB | DB helpers: QueryBuilder/connection access (getQb(), getCp()), lowercase result fetch (fetchAllByQb(), fetchOneByQb(), fetchIntByQb(), normaliseRows()), quoting, batchReplace() / batchUpsert() (rows with different column sets are written in one statement per set, so a short row never blanks or overwrites columns it does not carry), buildSingleTableSql(), schema helpers (columns, indexes, field sizes, multilang/multishop registration), view names |
SqlExpression | marks a raw SQL value (NOW(), UUID() ...) that ToolsDB::buildSingleTableSql() must not quote |
ListQueryConditionBuilder | turns the condition arrays of the loadAll* list queries into a SQL fragment |
ToolsConfig | shop config access: typed config params, saving options, aarr/constraint options of shop and DB config, multishop/shop ids, getModuleExists(), legacy metadata events, config export; uploadFile() with an extension whitelist (default UPLOAD_EXTENSIONS_DEFAULT; php/phtml/phar/.htaccess & co. are always rejected, also hidden inside the name) |
ToolsCast | scalar-safe casts for single mixed values (castString(), castInt(), castStringList() ...) |
ToolsArray | typed access to foreign array structures (arrayCheckString/Int/Float/Bool/Array/Object/List/StringList()) |
ToolsProp | typed access to foreign object structures (propCheckString/Int/Float/Bool/Array/Object()) |
ToolsApi | HTTP requests via Guzzle, cURL, file_get_contents or SOAP, with debug log; credentials (passwords, secrets, tokens, API keys, Authorization headers, URL userinfo, also inside JSON/XML/form bodies) are masked as *** before they reach the log (maskSensitiveData()/maskSensitiveString()); secrets without a telling name (e.g. an API key in a URL path) can be registered via addMaskValue() |
ToolsFTP | FTP/FTPS upload and sync, SFTP upload (phpseclib) |
ToolsFile | file and directory operations: move, delete, create paths, gzip/zip, permission checks (checkPathPermissions() creates a missing path silently and only logs an error if that fails), image/media file validation, article image cleanup |
ToolsDisk | free-disk-space guard for writers of many files; fails open when it cannot measure |
ToolsHtaccess | reads/writes the shop's .htaccess; fenced blocks owned by one module each |
ToolsXML | XML reading/writing (array ↔ XML, streamed item reading via XMLReader) |
ToolsExportCSV / ToolsExportXML | instance-based CSV / XML export writers |
ToolsYaml | YAML read/write |
ToolsString | string helpers: ASCII/normalisation, HTML → text, HTML sanitising, GTIN/ISBN cleanup, ISBN-aware search term groups, e-mail/date validation, UTF-8 handling, formatting of prices, durations, counts, bytes |
ToolsAddress | splits a street line into street and house number (viison/address-splitter) |
ToolsCountry | home country id, ISO alpha-2 code, country id by name |
ToolsBrowser | user agent checks on top of MobileDetect: mobile, tablet, in-app browser |
ToolsLang | translation of language idents |
ToolsLog | in-memory error/info/debug log buffer used by the other helpers |
ToolsMonologLogger | Monolog file loggers; flushToModuleFile() writes the ToolsLog buffer to log/<moduleId>/YYYY-MM-DD.log |
ToolsMonologDBLogger | Monolog logger writing into a DB table (handler: Vendor/Monolog/OxidDBHandler, table given by the caller) |
ToolsCache | clears shop caches: compile dir (tmp files, container, module and Twig template cache), page cache (via PageCachePurgerInterface), generated images, views, dynamic SEO urls |
ToolsSeo | create/delete static SEO urls, update SEO meta data |
ToolsOxid | OXID helpers: active user, admin checks, category path, writing attributes, manufacturers, category assignments and multilingual content; setCategoryTitle() reuses a category with the same title below the same parent instead of creating duplicates; setMultiLanguageContent() can create CMS pages inactive (last parameter $bActive) |
ToolsTheme | read/write/delete theme options |
ToolsModuleChain | inspects the class extension chain: is a method already implemented by another module, safe parent call with fallback, chain debugging |
ToolsComposerVersion | version and path of a module or theme from its composer data (used for 'version' in metadata.php) |
ToolsDynamicImageGenerator | scales an image file into another; configuration-free base of therealworld/dynimg-module |
overview: traits (src/Traits/)
| Trait | Purpose |
|---|---|
DataGetter | typed model field access: getTRWStringData(), getTRWIntData(), getTRWFloatData(), getTRWBoolData(), getTRWArrayData(), getTRWRawStringData(), getTRWDateData(), getTRWDateString(), getTRWDateTimestamp(), hasTRWValidDate() |
RequestGetter / StaticRequestGetter | typed request parameters (getTrwStringRequestData(), getTrwStringRequestEscapedData(), getTrwIntegerRequestData() ...) |
SessionGetter / StaticSessionGetter | typed session variables, set/delete, ensureTrwSessionStarted() |
CookieGetter / StaticCookieGetter | typed cookie read, set and delete |
Json / StaticJson | JSON decode/encode, JSON POST body, JSON response |
ServiceContainer / StaticServiceContainer | getServiceFromContainer() / getStaticServiceFromContainer() - thin wrappers around ContainerFacade::get() |
StaticModuleSettings | typed module settings for static *Config classes (getBoolSettingValue() ... getStringListSettingValue()) |
StaticModuleDebugLog | cached isDebugLog() flag on top of StaticModuleSettings |
ViewConfigTrait | shared getters for ViewConfig extensions |
LongRunningCommand | console commands over large lists: pacing, clean interruption, progress for terminal and log, typed options |
CommandOwnershipGuard | warns when a console command writes into a directory owned by another user (usually the web server); needs ext-posix, stays quiet without |
overview: services (src/Service/, services.yaml)
All classes in src/Service/ are registered public and autowired under their class name.
| Service | Purpose |
|---|---|
OxNewService | oxNew() wrapper, friendlier to static analysis |
ModuleService | module ids/paths (all, active, inactive), typed module setting values, module version, read/save module configuration |
CacheService | resets template, module, language and menu cache |
ClassExtensionChainService | reorders the class extension chain: move a module's extensions to the end or after another module, sort by dependency |
TemplateExtensionChainService | manages template_extension_chain.yaml: add (always on top = highest priority), remove, apply an order, sort |
ListLoaderService | batch loading of whole lists (articles, categories, contents, manufacturers, vendors, orders) for exports, feeds, sitemaps; with a limit the queries are sorted stably (by oxid, or oxid as tie-breaker after the sort field), so consecutive batches neither repeat nor skip rows |
In addition, services.yaml registers the core helpers under the ids trw.tools.api,
trw.tools.cache, trw.tools.config, trw.tools.DB, trw.tools.disk, trw.tools.file,
trw.tools.log, trw.tools.monologlogger, trw.tools.oxid, trw.tools.seo,
trw.tools.string, trw.tools.xml. Since these classes are static, calling them directly is
the usual way.
overview: contracts (src/Contract/)
Each interface is bound to a no-op default (Service/Null*), so a consumer always gets a usable
service. A module joins in by decorating the interface in its own services.yaml; because
a module's services.yaml is only loaded while the module is active, its contribution leaves
again on deactivation. Chain interfaces expect each implementation to combine its result with
the one of its $inner service instead of replacing it.
| Interface | Default | Purpose |
|---|---|---|
FormGuardInterface | NullFormGuard (everything passes) | form protection: isActive(), render(), verify() → FormGuardResult / FormGuardFailure; form keys in FormKey (user, newsletter, contact, details, password, suggest, withdrawal) |
CacheKeyCookieContributorInterface | NullCacheKeyCookieContributor | cookie-derived fingerprint for a static cache key (getFingerprint()) |
CacheKeyParamContributorInterface | NullCacheKeyParamContributor | query parameters that must stay in a static cache key (getAllowedParams()); base class AbstractCacheKeyParamContributor |
CacheExcludedControllerContributorInterface | NullCacheExcludedControllerContributor | frontend controllers a page cache must never serve (getExcludedControllers()); base class AbstractCacheExcludedControllerContributor |
PageCacheProbeInterface | NullPageCacheProbe (isWarm() = false) | lets a warmup ask a full page cache whether a url is cached, its key, usage (PageCacheUsage) |
PageCachePurgerInterface | NullPageCachePurger | empties a full page cache (purge()); kept apart from the read-only probe |
ArticleDeliveryPriceProviderInterface | NullArticleDeliveryPriceProvider (null) | shipping cost of an article to a country, e.g. for JSON-LD and feeds |
ArticleDeliveryTimeProviderInterface | NullArticleDeliveryTimeProvider (null) | delivery time (DeliveryTime, business days) |
MerchantReturnPolicyProviderInterface | NullMerchantReturnPolicyProvider (null) | the shop's return policy (MerchantReturnPolicy) for JSON-LD |
ConsentProviderInterface | NullConsentProvider (consent not required, everything allowed) | visitor consent for tracking/marketing code: isConsentRequired() (is a consent manager in use?), hasConsent(ident, category) (category fallback via CATEGORY_NECESSARY/PERFORMANCE/FUNCTIONAL/ADVERTISING); replaced by a consent module (e.g. trwcookieconsent), asked by tracking modules (e.g. trwmatomo) |
Example - a module bringing its own cache-relevant query parameter:
final class MyParamContributor extends AbstractCacheKeyParamContributor
{
protected function getOwnParams(): array
{
return ['myparam'];
}
}
services:
My\Module\Service\MyParamContributor:
decorates: TheRealWorld\ToolsPlugin\Contract\CacheKeyParamContributorInterface
arguments:
$inner: '@.inner'
Twig extensions (src/Twig/Extensions/)
| Name | Type | Purpose |
|---|---|---|
trw_form_guard(formKey) | function | renders the protection markup of the FormGuardInterface chain ('' if nothing protects the form) |
trw_form_guard_active(formKey) | function | true if any protection module is active for the form |
method_exists | filter | {% if oView\|method_exists('getPageHeaderSlider') %} |
trwtruncate | filter | shortens text without leaking HTML entities (unlike the core truncate); never combine with \|raw |
addhyphenstoisbn | filter | hyphenates an ISBN (9783423117371 → 978-3-423-11737-1); other values pass unchanged |
tests
tests/Unit/ holds PHPUnit tests (namespace TheRealWorld\ToolsPlugin\Tests\), currently for
TemplateExtensionChainService, the upload file name rule of ToolsConfig, the credential
masking of ToolsApi and the row grouping of the ToolsDB batch writers.
bugs and issues
Please report bugs and issues at https://bitbucket.org/therealworld/tools-plugin/issues.