История коммитов

.
refactor(system): use Cache library for counters
- Replace manual file cache (file_exists/filemtime/json_decode/file_put_contents) with Johncms\Cache remember()
- Merge duplicated counter queries: album/downloads/library/forum pairs now share one cached raw method and a single cache key
- Fix warning on accessing array offset on null when a cache file was empty or corrupted
- Drop unused Tools dependency from Counters constructor
.
fix(system): handle null path from parse_url in UserStat::determinePlace
Protocol-relative request URIs like //x.php are parsed as host without path, making parse_url return null and str_ireplace throw a TypeError.
.
feat(collections): add standalone install script for existing setups
- install/install_collections.php creates the 5 module tables via ModuleInstaller
- idempotent: skips when the collections table already exists
- optional --demo flag installs the sample "Blog" collection
- document the step in the 9.8 upgrade guide and CHANGELOG
.
fix(system): restore HTMLPurifier factory for global \HTMLPurifier service
The plain class registration overrode the factory definition, so the
container returned a default HTMLPurifier without figure/oembed/figcaption
support. Rich content images lost their figure.image wrapper, breaking
in-container image scaling and the magnific popup.

Closes #254
.
fix(collections): build item URL from its own section path in root listing
The root listing shows items from every section, but the detail URL was built
from the current listing path, so a sectioned item shown at the collection root
got /collection/code.html without its section segment and 404'd. PublicItemDTO
now carries sectionId, and the listing resolves each item's section path
(getPathTo, memoized per page) to build /collection/section-path/code.html.
.
feat(collections): auto-generate item slug from name
When the item code is left empty it is generated as a slug of the name
(Str::slug, transliterating Cyrillic) in SaveCollectionItemUseCase and made
unique within the collection/section with a numeric suffix on collision
(base, base-2, ...). An explicit code is still rejected on duplicate. The admin
validation now treats code as optional; the facade addItem generates it too.
.
feat(collections): use CKEditor for item preview text
Preview text becomes rich HTML like detail text: the admin item form renders it
through the CKEditor partial (scripts loaded once, on the preview editor), and
the public output sanitizes it via ItemContentFormatter in both the listing
(ListPublicItemsUseCase) and the detail page, rendering purified HTML instead of
nl2br+escape.
.
feat(collections): add public flag to gate URL access
Separate 'reachable by URL' from 'active': a new collections.public boolean
(default 1). A private collection stays fully manageable in admin and readable
via the API/facade, but mints no front URL and is excluded from the sitemap.

URL resolver and sitemap now go through getPublicCodeMap() (active AND public,
renamed from getActiveCodeMap); the code cache stores the filtered map. Admin
form gains a Public checkbox. Reserved-code validation stays flag-independent
so a private code can go public without a segment clash.
.
feat(collections): public CollectionsApi facade for third-party modules
Add CollectionsApi (CollectionsApiInterface), a stable DI-public facade over
repositories and item use cases. Read:
findCollection/getItems/countItems/getItem/getItemByCode. Write: addItem
(returns new id), updateItem, deleteItem, reusing code-uniqueness checks and
EAV value rebuild.

SaveCollectionItemUseCase::execute now returns the item id (int) instead of
bool; the admin controller derives the create/update flash from $id.
.
feat(collections): rich detail text with CKEditor and output sanitization
Completes iteration 4 (public output).

- The item admin form edits detail_text with CKEditor via a module ckeditor
partial (the admin theme ships CkeditorInputComponent.vue; system::app/ckeditor
only exists in the default theme, so collections carries its own).
- Content is stored raw; GetPublicItemUseCase sanitizes detail_text on output
through ItemContentFormatter (injects \HTMLPurifier) and the detail template
renders it as-is. preview_text stays plain (nl2br + escape).
- ItemContentFormatter test (real HTMLPurifier strips script/on* handlers, keeps
safe tags); GetPublicItemUseCase test updated with a passthrough purifier.