21 min readAug 10, 2026by jakub
Changelog
Current version: 1.2.0
Entries are merged from all thirteen module changelogs. A version heading
appears when any module shipped that version; the date is the core
qoliber/multiblog module's date for it.
1.2.0 — 2026-07-31
Multiblog
Fixed
- Post pages returned HTTP 500 for a published post with an empty
publish_date.Block\Post\PrevNext::getPreviousPost()/getNextPost()passed the null straight intoaddFieldToFilter, which binds''and makes MySQL raiseSQLSTATE[HY000] 1525 Incorrect TIMESTAMP value: '';isEnabled()caught its own exceptions but the getters did not, so the error escaped to the storefront. Both getters now return null when the current post has no publish date — there is no ordering anchor, so there are no previous/next links. Only instances with Show Prev/Next enabled were affected. Posts saved as published with the date left empty are a normal admin outcome, so this was reachable without doing anything unusual.
Changed
- BREAKING (permissions).
Qoliber_Multiblog::post_publish("Publish Posts") is now enforced. It was declared inacl.xmlfrom 1.0.0 but never checked — every post controller,Saveincluded, guards onQoliber_Multiblog::post— so withholding it from a role changed nothing and anyone who could edit a post could publish it. NewModel\Post\PublishPermissionchecks the resource, andController\Adminhtml\Post\Saveapplies it: a user without the permission who submits a post as Published gets it saved as a draft with a warning message. Only transitions into published are restricted; editing a post that is already published leaves it published, so a contributor fixing a typo cannot take live content offline. Review roles that had "Publish Posts" withheld — they could publish before this release and cannot now. - BREAKING. All blog search moved to
Qoliber_MultiblogSearch:Block\Search\Results,Block\Sidebar\Search,Controller\Search\Results,Model\Search\PostSearch, themultiblog_search_resultslayout, the search and sidebar-search templates, and_search.less. Core no longer ships any search code;/blog/searchreturns 404 without that module installed. Controller\Routerno longer builds controller class names from a hardcodedQoliber\Multiblog\Controller\...namespace. NewModel\Router\ControllerResolvertakes a di-injectable map keyed"<controller>/<action>", so satellite modules can own routes. Unmapped routes fall through to noroute.- The identical
forward()body previously duplicated inController\Router,Model\Router\CategoryUrlResolverandModel\Router\FlatUrlResolvernow lives only inControllerResolver.
Removed
Api\PostSearchInterface— wired in nodi.xml, never implemented, and its declared return type disagreed with the implementation.
Comments
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
GraphQL
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
Hyvä
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
Hyvä Commerce
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
Import
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
RSS
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
Search
Fixed
- Blog search silently degraded to MySQL whenever catalog search ran on a third-party engine.
OpenSearchAdapterresolved its client throughMagento\AdvancedSearch\Model\Client\ClientResolverand its index prefix throughMagento\Elasticsearch\Model\Config, both of which dispatch oncatalog/search/engine. Installing Elasticsuite sets that toelasticsuite, which is not a registered Magento client factory, so the client threwLogicException: There is no such client factory: elasticsuiteand the prefix resolved toNULL(producing the malformed index name_qoliber_multiblog_post). Model\Search\PostSearchfiltered on unqualified columns whileaddInstanceVisibilityFilter()joins tables that also carryis_active,instance_idandmeta_description, raisingSQLSTATE[23000] 1052 Column '<x>' in where clause is ambiguouson the MySQL path. All filter columns are now qualified withmain_table.
Added
- Own configuration section
qoliber_multiblog/search/*(hostname, port, index prefix, HTTP auth, timeout), independent ofcatalog/search. Model\Adapter\ClientBuilderbuilds the OpenSearch client directly from that config.Setup\Patch\Data\SeedSearchConnectionseeds the new section from Elasticsuite'ses_client/servers, then corecatalog/search/opensearch_*, then defaults. Never contacts the cluster and never blockssetup:upgrade.bin/magento qoliber:multiblog:search:checkverifies connectivity and index state on demand.- Blog search now owns its blocks, controller, layout, templates and LESS, moved here from
Qoliber_Multiblog.
Changed
- BREAKING (internal API).
Model\Search\PostSearchmoved to this module and its constructor now takesSearchServiceandLoggerInterface. The OpenSearch-vs-MySQL choice is an internal branch instead of a plugin. - Dropped
Magento_Search,Magento_ElasticsearchandMagento_OpenSearchfrom the module sequence and composer requirements; depends onopensearch-project/opensearch-phpdirectly.
Removed
Plugin\SearchPlugin— thearoundSearchwrapper existed only becausePostSearchlived in another module.
SEO Open Graph
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
SEO Rich Snippets
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
Sitemap
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
Social Sharing
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
Web API
Changed
- Version bump to 1.2.0 for the coordinated release. No functional changes in this module.
1.1.0 — 2026-04-27
Multiblog
Changed
- Admin menu nests under Content (
Magento_Backend::content) instead of being a top-level item. Editors find blog management next to Pages / Blocks / Widgets / Themes where they already look for content entry points. Children (Posts, Categories, Tags, Authors, Comments, Diagnostics) follow automatically because they all referenceparent="Qoliber_Multiblog::multiblog". Controller/Adminhtml/Instance/Save::executenow forwardsis_liveview_enabledfrom the POST payload onto the model. The instance Save controller cherry-picks fields (unlike Post / Category / Author, which use blanketsetData($data)); without this passthrough the Hyvä CMS Editor toggle inMultiblogHyvaCommercelooked enabled in the UI butInstanceRepositoryPlugin::afterSavesaw a null and never persisted the flag toqoliber_multiblog_instance_liveview.
Fixed
- Disabling a blog instance, or scoping it away from the current store, now takes its posts and categories offline on every public surface. Pre-1.1.0 the per-post
is_active/status/publish_datefilters checked the leaf only — posts whose parent instance was disabled remained reachable via direct URL, REST list/get, GraphQL, on-site search (MySQL fallback + OpenSearch path), and RSS feeds. NewPost\Collection::addInstanceVisibilityFilter(int $storeId)andCategory\Collection::addInstanceVisibilityFilter(int $storeId)joinqoliber_multiblog_instance+qoliber_multiblog_instance_storeand filter oninst.is_active = 1 AND inst_store.store_id IN (0, currentStore).Model\Visibility\InstanceVisibility::isInstanceVisible(int)(and the newisInstanceVisibleForStore(int, int)variant for callers that already know the store, e.g. GraphQL resolvers) is the single-entity counterpart used byController/Post/ViewandController/Category/ViewaftergetById(). Fixes audit P0-1. - Moving a category in admin (changing
parent_id) now propagates the new path to all descendants in the same transaction. Pre-1.1.0 only the saved row was rewritten; descendants kept stale paths andHelper\UrlBuilder::getCategoryUrlproduced broken canonical and category URLs and the XML sitemap exposed dead links until each descendant was touched manually. Fixes audit P1-2. - Iterating a Post collection no longer returns empty
category_ids/tag_ids/product_ids/related_post_ids.Collection::_afterLoadnow batch-loads all five relation tables (one SELECT each, grouped bypost_idin PHP). GraphQLmultiblogPostsreturned empty nested arrays before; this also unblocks listing/featured-slider blocks that read relations from the iterated items. Fixes audit P1-1. - Flat-URL resolver no longer accepts a bad trailing segment.
/blog/parent/garbagepreviously matched/blog/parentbecause the loop broke on the first missing segment but still forwarded to the deepest resolved category, splitting canonical signals across two URLs. The walker now requires every segment to resolve and returns null otherwise —Router::matchthen routes tocms/noroute/index. Fixes audit M-1.
Changed
- BREAKING for partial-save callers. Post / Category / Instance ResourceModels no longer rebuild every relation table on every save. Each
saveStoreIds/saveCategoryIds/saveProductIds/saveRelatedPostIds/saveTagIdsis gated by$object->hasData('xxx_ids'). New contract documented on the @api docblock of eachRepositoryInterface::save:- Omit a relation key from the save payload to leave existing assignments untouched.
- Pass
[]to explicitly clear them. Pre-1.1.0, a REST PUT carrying only{ "title": "X" }deleted every store / category / tag / product / related-post assignment for the post. To update the primary-category flag callers must also passcategory_ids(junction rebuild is wholesale). Admin form behaviour is unchanged — the multiblog_post_form always submits the relation arrays. Fixes audit P0-3.
Comments
Fixed
- Frontend comment submission no longer leaks an open redirect via
referer_url. Pre-1.1.0 the controller piped the POST value straight intosetUrl(); form-key validation prevented mass abuse but a phishing chain could still redirect the user off-host after submission.resolveSafeReferer()now accepts only same-origin absolute URLs or absolute paths (/...); anything else falls back to the storefront base URL. Fixes audit P1-3. - Luma
view/frontend/templates/post/comments.phtmlno longer fatals with “Cannot redeclare function renderComment” when the template is rendered twice in one request (nested includes, FPC warm path, multi-instance pages). The file-scoperenderCommentis now wrapped inif (!function_exists('renderComment')). Tier-2 plan is to port the HyvaCommentRendererViewModel pattern to Luma. Fixes audit P2.
Changed
POST /V1/multiblog/commentsis now declared inMultiblogComments/etc/webapi.xml(where thePublicCommentRepositoryInterfaceservice class lives) instead ofMultiblogWebapi/etc/webapi.xml.MultiblogComments'smodule.xmlnow sequencesMagento_Webapiand itscomposer.jsonrequiresmagento/module-webapi. DisablingQoliber_MultiblogCommentsinconfig.phpnow cleanly removes the route at boot rather than 500ing on missing service-class lookup. Fixes audit P0-2.etc/system.xmlwas relocated toetc/adminhtml/system.xml. Magento config-section discovery only loads admin-area files from the adminhtml subfolder, so theqoliber_multiblog/commentsgroup never appeared inStores → Configuration → Qoliber Multiblogeven though the module was enabled. After the move the group renders alongside General/SEO/Sidebar.
Added
qoliber_multiblog_comment.ip_addresscolumn (varchar(45)) is now declared indb_schema.xml.PublicCommentRepository::create()already wrotesetData('ip_address', ...)for moderation/audit purposes, but the column did not exist, so writes silently failed (Magento ResourceModel ignores unknown keys). Audit L-1.
GraphQL
Fixed
multiblogPostsandmultiblogCategoriesqueries respect the new instance-visibility rule. Resolvers now calladdInstanceVisibilityFilter($storeId)after the existing post-store / category-store join, so disabling a blog instance (or scoping it away from the current store) hides its posts and categories from the GraphQL surface in the same way it does on the storefront, REST, search, and RSS. Fixes audit P0-1 (GraphQL surfaces).- Single-entity resolvers
multiblogPost(post_id:)andmultiblogCategory(category_id:)now also enforce parent-instance visibility viaInstanceVisibility::isInstanceVisibleForStore($instanceId, $storeId)— a disabled or scoped-away instance threwNoSuchEntityon REST/storefront but still returned the entity over GraphQL. Fixes audit HIGH (single-resolver visibility). - Nested
category_ids,tag_ids,product_ids,related_post_idsare now populated on items returned bymultiblogPosts. Pre-1.1.0 the underlying Post collection only hydratedstore_ids, so every post in the response had empty arrays for the other four relations — fixed inQoliber_Multiblog 1.1.0by extendingPost\Collection::_afterLoad. Fixes audit P1-1 (consumed here).
Hyvä
Fixed
- Hyvä top-navigation no longer emits duplicate menu entries for an instance/category that is assigned to both "All Store Views" (store_id 0) and a concrete store.
AddBlogToHyvaNavigation::getActiveInstances()andgetInstanceCategories()nowGROUP BYafter joiningqoliber_multiblog_instance_store/qoliber_multiblog_category_store, matching what the Luma menu builder already did. Fixes audit L-2.
Hyvä Commerce
Added
- Hyvä CMS LiveView Editor support extended to instance, category, and author content types — alongside the existing
multiblog_post. Each new entity gets its own provider stack:- Schema:
qoliber_multiblog_<entity>_liveview(draft / published JSON +is_liveview_enabledflag),qoliber_multiblog_<entity>_liveview_version_history, andqoliber_multiblog_<entity>_liveview_tailwindcss(3 tables × 3 entities = 9 new tables, all CASCADE-deleting on the parent entity). - Provider classes:
MultiblogInstanceProvider,MultiblogCategoryProvider,MultiblogAuthorProvider, each registered under a dedicated entity-type key inHyva\CmsLiveviewEditor\Model\ProviderPool. - Frontend swap plugins:
InstanceInterfacePlugin::afterGetDescription,CategoryInterfacePlugin::afterGetDescription,AuthorInterfacePlugin::afterGetBio— same preview/published switching behaviour as the post variant; falls back to the original WYSIWYG body on any error. - DataProvider plugins to hydrate
is_liveview_enabledinto each form, repository plugins to sync that flag back into the liveview row on save. - System config:
hyva_cms/multiblog/{instance,category,author}_enabled+..._enabled_by_defaultflags, gated on the masterhyva_cms/general/enabledswitch.
- Schema:
- Inline editor pattern on all four entity forms (post / instance / category / author), mirroring
cms_page_form.xml:- Toggle (
is_liveview_enabled) renders alongside the WYSIWYG description / content / bio field. - When the toggle is on, the WYSIWYG is hidden and a preview iframe + "Edit with Hyvä CMS" link appear in its place.
- Driven by a Multiblog-specific port of
open-liveview.phtml(Qoliber_MultiblogHyvaCommerce::ui/open-liveview.phtml) that walks theuiRegistryto flip the WYSIWYG field'svisibleobservable directly. The upstreamswitcherConfigrule fires before PageBuilder's wysiwyg field finishes wiring, so the hide call is dropped — our template applies it post-init via thedataProvider.on('data.is_liveview_enabled', …)subscription. Each form's merge XML passes its WYSIWYG target through a newwysiwyg_targetblock argument.
- Toggle (
- Close-editor → entity edit page routing fixed for all four entity types. Each
core-settings/<entity>.phtmltemplate now dispatchesliveview:set-return-urlson Alpineinit(), telling the editor's go-back handler to land back on the admin edit form (or the entity index for new records). New core-settings templates added for instance / category / author (post got the dispatch line added). Without this dispatch the editor'sliveview-composer.phtmlleavesreturnUrls.entityPathat its admin-dashboard default, so closing the editor sent the merchant to the dashboard. liveview_editor.xmllayout now registerscore-settings.multiblog_{instance,category,author}blocks and the matching listings entries, so the editor toolbar shows the correct entity label and the navigator can switch between Multiblog content types.multiblog/{instance,category,author}/editadmin routes added toHyva\CmsLiveviewEditor\Model\Security\IsValidAdminPreviewRequest::allowedRoutesso auto-CSP applies the correct frame-src on multi-domain setups.
Fixed
- PHPStan level-8 cleanup:
LiveviewPostRepository::getByPostIdnow passes the post id toaddFieldToFilteras['eq' => $postId](the int form was rejected);save()checksinstanceof \Magento\Framework\Model\AbstractModelbefore calling$resource->save()instead of using a@varcast;CouldNotSaveExceptionis rethrown only on\Exception(not\Throwable) so the cause-type contract holds.Controller/Adminhtml/Link/MultiblogPosts::executedrops the no-op?? []ongetStoreIds()(declared asarrayin the interface).
Notes
- The toolbar
Block\Adminhtml\<Entity>\Edit\HyvaCmsButtonclasses are still wired but no longer referenced from the form ui_components. The inline "Edit with Hyvä CMS" link in the new preview block supersedes them; the classes stay in the codebase as a fallback API for downstream extensions and may be removed in 1.2.0.
Import
Fixed
- WordPress author import now sets
instance_idon the savedqoliber_multiblog_authorrow. Pre-1.1.0EntityImporter::importAuthors()accepted just($authors, $dryRun, $startOffset)and never propagated the parent import's instance, so imported authors were instance-orphans — author URLs/listings/REST/GraphQL are instance-aware and silently hid them. The signature gains a requiredint $instanceIdbetween the array and the dry-run flag;ImportWordPressCommandalready has the value ($jobId = $importJobTracker->startJob(self::JOB_TYPE, $filePath, $instanceId)) and now threads it through. Fixes audit M-4.
Changed
- BREAKING (internal API).
EntityImporter::importAuthors()signature changed from(array $authors, bool $dryRun = false, int $startOffset = 0)to(array $authors, int $instanceId, bool $dryRun = false, int $startOffset = 0). Direct callers must update; the bundled CLI command was updated.
RSS
Fixed
- All four feed controllers (instance, category, tag, author) now apply the instance-visibility filter to the post collection. Pre-1.1.0 a feed for an instance whose
is_activewas off, or whose store assignment did not include the current store, would still render posts (post-level filters caught most cases but the parent-store leak slipped through). Fixes audit P0-1 (RSS surfaces).
Search
Fixed
Plugin\SearchPlugin::aroundSearch(the OpenSearch hot path) now appliesaddInstanceVisibilityFilter($storeId)to the rebuilt result-set collection. Pre-1.1.0, a post indexed in OpenSearch could surface in search results even after its parent instance was disabled or scoped to a different store. Fixes audit P0-1 (on-site search). Same fix lands on the MySQL fallback inQoliber\Multiblog\Model\Search\PostSearch.SearchService::search()no longer swallows OpenSearch failures and returns[]. The empty-array fallback was indistinguishable from a legitimate "0 hits" result, soSearchPluginrendered "no posts found" during outages instead of falling back to the MySQL LIKE search. The exception now propagates andSearchPlugin's outertry/catchcalls$proceed()to hand the query to the MySQL implementation. Fixes audit M-3a.OpenSearchAdapter::search()no longer drops indexed posts whosepublish_dateis null. Therange publish_date <= nowfilter excluded null values, while the MySQL collection accepts them via[lteq=>now] OR [null=>true]. Replaced with abool > shouldclause covering both branches. Fixes audit M-3b.
Changed
- BREAKING (internal API).
SearchService::__construct()no longer acceptsPsr\Log\LoggerInterface(the dependency was only used by the now-removed swallow-and-log path). Direct constructor callers should drop the second argument; DI wiring is unaffected.
SEO Rich Snippets
Fixed
BlogPostDataExtractornow formats postdatePublished/dateModifiedas UTC explicitly vianew \DateTimeImmutable($date, new \DateTimeZone('UTC')). Pre-1.1.0 the constructor used the host timezone, which produced unstable JSON-LD across servers and tripped the unit-test expectation. Output is now a stableY-m-d\TH:i:s+00:00regardless of where Magento is running.
Sitemap
Fixed
PostItemProviderandCategoryItemProvidernow scope the visible-instance cache by store. Pre-1.1.0 the cache was keyed only byinstance_id, so when the same provider instance was reused across multiple stores during one sitemap run, later stores reused the first store's instance-visibility set — store B's sitemap could include posts whose parent instance was hidden from store B. Cache structure changed toarray<int $storeId, array<int $instanceId, InstanceInterface>>and the existence check / lookup were updated accordingly. Fixes audit M-2.
Social Sharing
Fixed
- Post-page Open Graph / Twitter Card meta tags now render in
<head>. Pre-1.1.0multiblog_post_view.xmlplaced the OpenGraph block in thebefore.body.endcontainer, but social-card crawlers (Facebook scraper, Slack/Discord unfurl, Twitter card validator) only inspect the head — the post-level OG payload was effectively invisible to them. Block moved tohead.additional, matching the category and instance layouts that already used it. Fixes audit M-5.
Web API
Fixed
PublicPostRepository::getListandgetByIdnow apply the instance-visibility rule (post and parent instance must be active and store-scoped). Pre-1.1.0 only the per-postis_active/status/publish_datechecks ran, so a post under a disabled instance was still served via REST. Same fix onPublicCategoryRepository. Fixes audit P0-1 (REST surfaces).
Changed
POST /V1/multiblog/commentsis no longer declared in this module'swebapi.xml. The route lived here historically but the service class belongs toQoliber_MultiblogComments, so disabling Comments left a dangling route entry that 500'd at boot. Route moved toMultiblogComments/etc/webapi.xml(see Comments 1.1.0). Fixes audit P0-2.
1.0.1 — 2026-04-21
Multiblog
Fixed
- Admin entity saves (category, post, instance, tag, author) no longer persist an empty-string primary key through
setData(). The Save controllers null out the empty hidden PK before$model->setData($data), matching Magento coreMagento_Cmsconvention — was silently redirecting to the grid with no success message on new-record saves. - Category WYSIWYG description rendered as HTML-escaped text on the frontend.
Block\Category\View::getFilteredDescription()now runs the content through\Magento\Cms\Model\Template\FilterProvider::getPageFilter()(widgets, directives, inline<style>all parse correctly), mirroring the existing post-content flow. Both the Luma and Hyvacategory/view.phtmltemplates were updated. - Swagger / REST schema generation at
/rest/all/schema?services=...returned HTTP 500 (Each method must have a doc block). Magento'sWebapi\Reflection\TypeProcessorrequires PHPDoc@return/@paramon every service-contract method — PHP 8 native return types are not enough. Added annotations to all methods onApi\Data\{Author,Category,Instance,Post,Tag}Interface(196 methods).
Changed
- UI form
requestFieldNameunified withprimaryFieldName(xxx_id) in all five admin forms, matching the modernMagento_Cmspattern. Admin edit URLs are now/edit/xxx_id/N; Edit / Delete / Preview / Duplicate controllers,GenericButton/DeleteButton/PreviewButton/DuplicateButtonURL builders, and the Post product-grid tabs all read and emit the new param name.
Comments
Fixed
- Swagger / REST schema generation at
/rest/all/schema?services=qoliberMultiblogCommentsPublicCommentRepositoryV1returned HTTP 500 (Each method must have a doc block). Added PHPDoc@return/@paramannotations to all 22 methods onApi\Data\CommentInterfaceso Magento'sWebapi\Reflection\TypeProcessorcan build the schema.
Hyvä
Fixed
- Category description in the Hyva
category/view.phtmltemplate was HTML-escaped, so WYSIWYG widgets, inline<style>, and page-builder directives rendered as literal text. Template now calls$block->getFilteredDescription()(provided byQoliber\Multiblog\Block\Category\View, 1.0.1+) and emits the result with@noEscape. Added the Tailwindprose max-w-nonewrapper for consistent typography on long-form descriptions.
1.0.0 — 2026-04-13
Multiblog
Fixed
- Block cache key collision causing siblings to render the first sibling's HTML when
block_htmlcache was enabled (affectedPost\View,Category\View,Instance\View— addedgetNameInLayout()togetCacheKeyInfo()) - Author URL generation: slug-based, instance auto-resolved from
AuthorInterface, addedgetAuthorUrlBySlug()for cached listing contexts - Post content now processed through CMS/PageBuilder filter
- Product cards on post pages render proper grid with special price strikethrough
- Cross-instance post disclosure: post view verifies matched instance to prevent leaking posts across instances
parameter.implicitlyNullabledeprecations in 3 Grid Collection classes (PHP 8.4 compatibility)- URL suffix config now actually applied (previously dead config)
- Unique
(instance_id, url_key)constraint on tags with dedup data patch
Added
- PHP 8.1–8.4 compatibility range
- URL collision admin page + CLI diagnostic
- Per-post OG overrides (
og_title,og_description,og_image,twitter_card_type) - Image sitemap entries + paginated-listings canonical/robots + locale alternates
- Full
BlogPostingJSON-LD withBreadcrumbList,og:imagedimensions,article:tag - Scoped-to-instance authors (new FK + data patch)
is_primaryflag onqoliber_multiblog_post_categoryfor deterministic canonical- Async view count via
qoliber_multiblog_post_view_log+ hourly rollup cron + bot detection - Block-level cache identities +
PostRelatedContentViewModel extraction - Category-path single-query URL resolution
Routerdecomposed intoFlatUrlResolver+CategoryUrlResolverstrategies- Luma topmenu navigation tree caching
- Meta-title suffix via observer
Changed
- Composer package renamed from
qoliber/module-multiblogtoqoliber/multiblog - Service contract interfaces expanded to cover all persisted fields (BREAKING)
- Router caches instance route lookups
Security
- WordPress import hardened against SSRF and OOM attacks
Comments
Added
- Frontend comment submission with honeypot + rate limit.
- Email notifications to admin on new submissions and to commenter on approval (both configurable).
- Admin moderation controllers: approve, reject, delete, bulk actions.
- Nested-reply rendering with configurable depth (Hyva ViewModel).
- IP address column on
qoliber_multiblog_commentfor moderation context. CommentRepositoryPlugincentralizing side effects (count update + approval notification) viaafterSavewith status transition detection.- Integration tests for submission and moderation flows.
Fixed
- Comment submission enforces full post visibility (
is_active = 1,status = published,publish_date <= now()) and honours instanceallow_commentsflag.
GraphQL
Added
- Batch resolvers on nested relationships (
BatchResolverInterface) — eliminates N+1 acrossauthor,category,tag, and instance traversal. SortInputtype withfield+directionon all list queries.- Author resolvers no longer expose
emailfield (information disclosure).
Changed
- Composer package renamed from
qoliber/module-multiblog-graph-qltoqoliber/multiblog-graph-ql. - PHP constraint bumped to 8.1–8.4.
Security
emailstripped fromMultiblogAuthorGraphQL type.
Hyvä
Added
- Hyvä-compatible templates for instance, category, post, author, tag, and search views.
- Hyvä navigation integration via
AddBlogToHyvaNavigationplugin. - Tailwind-based styling for all frontend blocks, including comment thread and share buttons.
CommentRendererViewModel for configurable nested comment rendering.- Accessibility landmarks (
<nav>,aria-label) on post and listing templates.
Changed
- Composer package renamed to
qoliber/multiblog-hyva. - PHP constraint bumped to 8.1–8.4.
Hyvä Commerce
Added
- Initial release. Bridges Multiblog posts into the Hyvä CMS LiveView Editor.
MultiblogPostProviderimplementsHyva\CmsLiveviewEditor\Api\ProviderInterfaceand is registered as themultiblog_postcontent type in the HyväProviderPool.- Schema:
qoliber_multiblog_post_liveview(draft / published JSON +is_liveview_enabledflag),qoliber_multiblog_post_liveview_version_history, andqoliber_multiblog_post_liveview_tailwindcss. All three CASCADE-delete on the parent post. Qoliber\MultiblogHyvaCommerce\Block\Adminhtml\Post\Edit\HyvaCmsButtonadds an "Open in Hyvä CMS" toolbar button to the admin post edit form, deep-linking intoliveview-editor/page/edit?type=multiblog_post&id=N.Plugin\Block\Post\View::afterGetFilteredContentswaps the post body for the Hyvä CMS-rendered component tree whenis_liveview_enabledis set — Luma and Hyvä themes both inherit the swap because they call the same$block->getFilteredContent()getter.- README documents Tier 1 scope and what is deferred to Tier 2 (instances / categories / tags content types, schedule provider, version history UI, Tailwind JIT, AI translation bridge).
Notes
- Requires an active Hyvä Commerce CMS license — this module is the bridge only and depends on
hyva-themes/commerce-module-cmsfor the editor itself.
Import
Added
- Resumable imports via
qoliber_multiblog_import_jobtable — restart a failed import from the last successfully processed chunk. - Chunked commits with transaction boundaries and duplicate detection.
url_key-based resolution for updating existing entities instead of always inserting.- WordPress author linking by login/email to existing
qoliber_multiblog_authorrows.
Security
- WordPress import hardened against SSRF and OOM attacks: blocked internal IPs, disabled XML entity expansion, enforced size limits on media downloads.
Changed
- Composer package renamed from
qoliber/module-multiblog-importtoqoliber/multiblog-import. - PHP constraint bumped to 8.1–8.4.
RSS
Added
- Server-side feed cache with publish-event invalidation (feeds no longer rebuilt on every request).
- Unit and integration tests covering XML generation and per-scope feeds.
Changed
- Composer package renamed from
qoliber/module-multiblog-rsstoqoliber/multiblog-rss. - PHP constraint bumped to 8.1–8.4.
Search
Added
- Chunked reindex by post ID range (reindex no longer loads all posts in memory).
- MView subscriptions extended to relation tables (
post_category,post_tag) — edits to relations now trigger reindex. - Unit test coverage on search plugin, service, and reindex command.
Changed
- Composer package renamed from
qoliber/module-multiblog-searchtoqoliber/multiblog-search. - PHP constraint bumped to 8.1–8.4.
Sitemap
Added
- Initial release: core sitemap integration extracted from the main module into its own package.
ItemProviderInterfaceimplementation exposing posts, categories, and instances to the Magento sitemap generator.- Image sitemap entries on post items (featured image URL + title).
- Per-entity priority and change-frequency configuration via
system.xml. - Multi-store support: each store's sitemap only contains entities assigned to that store.
Changed
- Replaces the earlier observer-based approach; sitemap entries now surface through the standard Magento sitemap pipeline with correct lastmod handling.
Social Sharing
Fixed
- Open Graph meta tag property names were HTML-encoded (
og:title→og:title), rendering them invisible to crawlers. Property names now allowlist-filtered before emission.
Added
- Listing pages render share buttons when the instance config's
show_on_listingflag is set.
Changed
- Composer package renamed from
qoliber/module-multiblog-social-sharingtoqoliber/multiblog-social-sharing. - PHP constraint bumped to 8.1–8.4.
Web API
Added
- Public REST endpoint for comment submission (
POST /V1/multiblog/comment) with rate limiting and validation. - REST route ownership consolidated into this module (removed duplicate routes from core).
Changed
- Composer package renamed from
qoliber/module-multiblog-webapitoqoliber/multiblog-webapi. - PHP constraint bumped to 8.1–8.4.
0.11.0 — 2026-04-08
Multiblog
Added
- Store scoping on all frontend listing blocks (post collections now join
qoliber_multiblog_post_storeto filter by current store) - Related blog posts block on product pages (
catalog_product_viewlayout) - Post preview from admin with secure token-based frontend rendering (Preview button on post edit form)
- Post duplication controller (Duplicate button on post edit form, creates a draft copy)
Fixed
- Posts restricted to specific stores no longer appear on other stores in instance, category, author, tag, sidebar, search, and prev/next listings
0.10.0 — 2026-04-08
Multiblog
Added
- Category image field for listing and detail pages
- Instance description with configurable position control (above/below posts)
- Pagination-aware canonical URLs for listing pages
- Featured section limited to page 1 only
- Category-based canonical URLs for posts using category URL structure
- Store scoping improvements: removed duplicate core GraphQL schema/resolvers in favour of MultiblogGraphQl companion module
- Social share buttons on instance and category listing layouts (makes
show_on_listingconfig functional)
Fixed
- Removed core
schema.graphqlsandModel/Resolver/directory that duplicated MultiblogGraphQl resolvers without store filtering - Playwright test specs: replaced invalid
toHaveCount({ minimum: 1 })with propertoBeVisible()assertions - Removed outdated
INTEGRATION_TESTING.md(testing documented in main README)
0.9.0 — 2026-04-08
Multiblog
Added
- Multi-instance blog architecture with independent route, category, and post management per instance
- Blog Instance entity with CRUD, admin grid/form, and flexible URL structure configuration
- Post entity with full lifecycle: draft, published, archived statuses
- Category entity with hierarchical tree structure and parent validation
- Tag entity with full CRUD, admin grid/form, tag chips UI with autocomplete in admin
- Comment entity with basic admin scaffolding (no frontend comments UI)
- Standalone Author entity with full CRUD, admin grid/form (name, bio, image, social links, URL slugs)
- Featured post flag (
is_featuredtoggle on posts) - Featured article and featured categories on instance homepage
- Post display modes (list / grid) configurable per instance
- Previous / next post navigation (configurable per instance via
show_prev_nexttoggle) - Navigation menu integration with per-instance show/hide and sort order
- Sidebar widget removal (compare, wishlist) configurable per instance
- Image upload with featured image support and image resize helpers
- UrlBuilder service for consistent URL generation across all templates
- Sidebar widgets: search, recent posts, popular posts, categories tree, tag cloud, archive
- Post search with full-text matching across title, content, excerpt
- Pagination on all listing pages (instance, category, author, tag views)
- Post view counter with configurable frontend display
- Reading time estimation (auto-calculated on save)
- Related products on posts (with product cache tag propagation)
- Related posts
- Scheduled publishing with cron auto-publish every 5 minutes
- SEO: meta title, description, keywords per entity
- SEO: canonical URL rendering on post, category, and instance controllers
- SEO: per-entity meta robots configuration on post, category, and instance controllers
- SEO: basic JSON-LD structured data (BlogPosting/NewsArticle) block and template
- SEO: proper Magento breadcrumbs integration via Breadcrumbs block
- Sitemap integration (posts, categories, instances with priorities)
- Multi-store support with store filtering on all frontend controllers
- Composite DB indexes for routing performance
- Unique constraints on (instance_id, url_key) to prevent URL collisions
- Admin configuration panel (system.xml) with 30+ settings
- ACL resources for granular admin permissions
- Cache identity support with proper cache tags including product references
- Configurable date display, author display, and reading time visibility in templates
- Full integration test suite
- Unit tests for URL builder, reading time, post scheduler
- adminhtml and frontend _module.less CSS files
Planned
- Open Graph tags rendering (config stub exists, rendering deferred to companion module)
Fixed
- Layout XML child blocks now use correct block classes (were rendering empty)
- Template URLs now use UrlBuilder service (were generating broken 404 links)
- PostInterface now declares all methods formally (were only @method annotations)
- Store filtering enforced on all frontend controllers
- Author controller validates author existence before rendering
- Category tree path building moved to _afterSave (was using null ID in _beforeSave)
- Hardcoded status strings replaced with PostInterface constants
- Category/Instance controller indentation errors
- Module composer.json now declares magento/module-sitemap dependency
Security
- Frontend controllers enforce store assignment
- Published + active + date filters on all public-facing queries
GraphQL
Added
- Initial release of the GraphQL API layer for
Qoliber_Multiblog. multiblogInstancequery -- fetch a single blog instance by ID with active and store checks.multiblogInstanceByRoutequery -- fetch a blog instance by URL route with active and store checks.multiblogInstancesquery -- list blog instances with filter support, active and store enforcement.multiblogPostquery -- fetch a single post by ID with published, active, publish date, and store checks.multiblogPostsquery -- list posts with filter support and mandatory published/active/store enforcement.multiblogCategoryquery -- fetch a single category by ID with active and store checks.multiblogCategoriesquery -- list categories with filter support, active and store enforcement.multiblogTagquery -- fetch a single tag by ID.multiblogTagsquery -- list tags with filter support.MultiblogTagGraphQL type andMultiblogTagswrapper type.MultiblogTagFilterInputfilter input type.tag_idsfield added toMultiblogPosttype.FilterApplierhelper class for applyingeq,in, andlikefilter conditions to search criteria.- Store-scoped security: all resolvers read store context from GraphQL extension attributes.
- Post security: posts are only returned when
is_active = 1,status = 'published', andpublish_date <= now(). - Instance/category security: only active entities assigned to the current store are returned.
Fixed
- Filter arguments (
$args['filter']) are now applied to search criteria. The original core module resolvers ignored filter arguments entirely. - Store context is now derived from GraphQL context (
$context->getExtensionAttributes()->getStore()) instead ofStoreManagerInterface, which is the correct approach for GraphQL resolvers.
Import
Added
- WordPress WXR/XML import command (
multiblog:import:wordpress) - CSV import command (
multiblog:import:csv) supporting posts, categories, and tags - CSV export command (
multiblog:export:csv) supporting posts, categories, and tags - Dry-run mode for all import commands
- WordPress post status mapping (publish, draft, private)
- WordPress attachment/featured image import support
- Category parent relationship preservation during import
- Category and tag name-based resolution for post associations
RSS
Added
- Instance RSS feed controller (
/multiblog-rss/feed/instance/instance_id/{id}) - Category RSS feed controller (
/multiblog-rss/feed/category/instance_id/{id}/category_id/{id}) - Author RSS feed controller (
/multiblog-rss/feed/author/instance_id/{id}/author_id/{id}) - Tag RSS feed controller (
/multiblog-rss/feed/tag/instance_id/{id}/tag_id/{id}) - RSS 2.0 XML generator with Atom namespace support
- Shared RSS data provider for building feed data from post collections
- FeedLink block for RSS auto-discovery in HTML head
- Layout XML for instance and category view pages
- Default configuration: enabled with 20 posts per feed
Search
Added
- Initial release of OpenSearch-powered blog search
- BlogPostIndexer with full and partial reindex support
- Mview subscription on
qoliber_multiblog_posttable for real-time indexing - OpenSearch adapter with index management, bulk indexing, and search
- SearchService with availability checks
- Frontend plugin on
PostSearch::searchto route queries through OpenSearch - Automatic fallback to MySQL LIKE search when OpenSearch is unavailable
- CLI command
multiblog:search:reindexfor manual reindexing with progress output - Multi-field search with relevance boosting (title 3x, excerpt/tags/categories 2x)
- Fuzzy matching support via OpenSearch
- Relevance-ordered results preserved in post collection
SEO Open Graph
Added
- Initial release.
PostResolverimplementingResolverInterfacefor blog post OG tags (article type, published_time, author, section).CategoryResolverfor blog category OG tags (website type).InstanceResolverfor blog instance OG tags (website type).AuthorResolverfor author page OG tags (profile type).- DI registration of all resolvers in
GetTagResolverservice with\Proxylazy loading. - Layout handles with OG namespace prefixes for article and profile types.
- Layout overrides to remove MultiblogSocialSharing fallback OG blocks on all view pages.
SEO Rich Snippets
Added
- Initial release.
BlogVariableProviderimplementingVariableProviderInterfacewith 15 blog post variables.BlogValueResolverimplementingValueResolverInterfacefor resolving{{blog.*}}template placeholders.- DI registration for
SchemaVariables,VariableExtractor, andJsonLdblock. - Blog page type registration (post, category, instance, author) in
PageTypesource model. - Layout overrides to remove core Multiblog structured data blocks on all view pages.
Social Sharing
- Initial release
- Social sharing buttons block with configurable platforms
- Open Graph and Twitter Card meta tag fallback for posts, categories, and instances
- Copy Link button with clipboard API support
- Frontend LESS styling for share buttons
Web API
Added
- Initial release
- REST API endpoints for Instance, Post, Category, Tag
- Public endpoints enforce published + active + store filtering
- Admin endpoints with ACL protection