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 into addFieldToFilter, which binds '' and makes MySQL raise SQLSTATE[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 in acl.xml from 1.0.0 but never checked — every post controller, Save included, guards on Qoliber_Multiblog::post — so withholding it from a role changed nothing and anyone who could edit a post could publish it. New Model\Post\PublishPermission checks the resource, and Controller\Adminhtml\Post\Save applies 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, the multiblog_search_results layout, the search and sidebar-search templates, and _search.less. Core no longer ships any search code; /blog/search returns 404 without that module installed.
  • Controller\Router no longer builds controller class names from a hardcoded Qoliber\Multiblog\Controller\... namespace. New Model\Router\ControllerResolver takes 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 in Controller\Router, Model\Router\CategoryUrlResolver and Model\Router\FlatUrlResolver now lives only in ControllerResolver.

Removed

  • Api\PostSearchInterface — wired in no di.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.

Fixed

  • Blog search silently degraded to MySQL whenever catalog search ran on a third-party engine. OpenSearchAdapter resolved its client through Magento\AdvancedSearch\Model\Client\ClientResolver and its index prefix through Magento\Elasticsearch\Model\Config, both of which dispatch on catalog/search/engine. Installing Elasticsuite sets that to elasticsuite, which is not a registered Magento client factory, so the client threw LogicException: There is no such client factory: elasticsuite and the prefix resolved to NULL (producing the malformed index name _qoliber_multiblog_post).
  • Model\Search\PostSearch filtered on unqualified columns while addInstanceVisibilityFilter() joins tables that also carry is_active, instance_id and meta_description, raising SQLSTATE[23000] 1052 Column '<x>' in where clause is ambiguous on the MySQL path. All filter columns are now qualified with main_table.

Added

  • Own configuration section qoliber_multiblog/search/* (hostname, port, index prefix, HTTP auth, timeout), independent of catalog/search.
  • Model\Adapter\ClientBuilder builds the OpenSearch client directly from that config.
  • Setup\Patch\Data\SeedSearchConnection seeds the new section from Elasticsuite's es_client/servers, then core catalog/search/opensearch_*, then defaults. Never contacts the cluster and never blocks setup:upgrade.
  • bin/magento qoliber:multiblog:search:check verifies 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\PostSearch moved to this module and its constructor now takes SearchService and LoggerInterface. The OpenSearch-vs-MySQL choice is an internal branch instead of a plugin.
  • Dropped Magento_Search, Magento_Elasticsearch and Magento_OpenSearch from the module sequence and composer requirements; depends on opensearch-project/opensearch-php directly.

Removed

  • Plugin\SearchPlugin — the aroundSearch wrapper existed only because PostSearch lived 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 reference parent="Qoliber_Multiblog::multiblog".
  • Controller/Adminhtml/Instance/Save::execute now forwards is_liveview_enabled from the POST payload onto the model. The instance Save controller cherry-picks fields (unlike Post / Category / Author, which use blanket setData($data)); without this passthrough the Hyvä CMS Editor toggle in MultiblogHyvaCommerce looked enabled in the UI but InstanceRepositoryPlugin::afterSave saw a null and never persisted the flag to qoliber_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_date filters 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. New Post\Collection::addInstanceVisibilityFilter(int $storeId) and Category\Collection::addInstanceVisibilityFilter(int $storeId) join qoliber_multiblog_instance + qoliber_multiblog_instance_store and filter on inst.is_active = 1 AND inst_store.store_id IN (0, currentStore). Model\Visibility\InstanceVisibility::isInstanceVisible(int) (and the new isInstanceVisibleForStore(int, int) variant for callers that already know the store, e.g. GraphQL resolvers) is the single-entity counterpart used by Controller/Post/View and Controller/Category/View after getById(). 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 and Helper\UrlBuilder::getCategoryUrl produced 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::_afterLoad now batch-loads all five relation tables (one SELECT each, grouped by post_id in PHP). GraphQL multiblogPosts returned 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/garbage previously matched /blog/parent because 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::match then routes to cms/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 / saveTagIds is gated by $object->hasData('xxx_ids'). New contract documented on the @api docblock of each RepositoryInterface::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 pass category_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 into setUrl(); 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.phtml no 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-scope renderComment is now wrapped in if (!function_exists('renderComment')). Tier-2 plan is to port the Hyva CommentRenderer ViewModel pattern to Luma. Fixes audit P2.

Changed

  • POST /V1/multiblog/comments is now declared in MultiblogComments/etc/webapi.xml (where the PublicCommentRepositoryInterface service class lives) instead of MultiblogWebapi/etc/webapi.xml. MultiblogComments's module.xml now sequences Magento_Webapi and its composer.json requires magento/module-webapi. Disabling Qoliber_MultiblogComments in config.php now cleanly removes the route at boot rather than 500ing on missing service-class lookup. Fixes audit P0-2.
  • etc/system.xml was relocated to etc/adminhtml/system.xml. Magento config-section discovery only loads admin-area files from the adminhtml subfolder, so the qoliber_multiblog/comments group never appeared in Stores → Configuration → Qoliber Multiblog even though the module was enabled. After the move the group renders alongside General/SEO/Sidebar.

Added

  • qoliber_multiblog_comment.ip_address column (varchar(45)) is now declared in db_schema.xml. PublicCommentRepository::create() already wrote setData('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

  • multiblogPosts and multiblogCategories queries respect the new instance-visibility rule. Resolvers now call addInstanceVisibilityFilter($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:) and multiblogCategory(category_id:) now also enforce parent-instance visibility via InstanceVisibility::isInstanceVisibleForStore($instanceId, $storeId) — a disabled or scoped-away instance threw NoSuchEntity on REST/storefront but still returned the entity over GraphQL. Fixes audit HIGH (single-resolver visibility).
  • Nested category_ids, tag_ids, product_ids, related_post_ids are now populated on items returned by multiblogPosts. Pre-1.1.0 the underlying Post collection only hydrated store_ids, so every post in the response had empty arrays for the other four relations — fixed in Qoliber_Multiblog 1.1.0 by extending Post\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() and getInstanceCategories() now GROUP BY after joining qoliber_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_enabled flag), qoliber_multiblog_<entity>_liveview_version_history, and qoliber_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 in Hyva\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_enabled into 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_default flags, gated on the master hyva_cms/general/enabled switch.
  • 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 the uiRegistry to flip the WYSIWYG field's visible observable directly. The upstream switcherConfig rule fires before PageBuilder's wysiwyg field finishes wiring, so the hide call is dropped — our template applies it post-init via the dataProvider.on('data.is_liveview_enabled', …) subscription. Each form's merge XML passes its WYSIWYG target through a new wysiwyg_target block argument.
  • Close-editor → entity edit page routing fixed for all four entity types. Each core-settings/<entity>.phtml template now dispatches liveview:set-return-urls on Alpine init(), 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's liveview-composer.phtml leaves returnUrls.entityPath at its admin-dashboard default, so closing the editor sent the merchant to the dashboard.
  • liveview_editor.xml layout now registers core-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}/edit admin routes added to Hyva\CmsLiveviewEditor\Model\Security\IsValidAdminPreviewRequest::allowedRoutes so auto-CSP applies the correct frame-src on multi-domain setups.

Fixed

  • PHPStan level-8 cleanup: LiveviewPostRepository::getByPostId now passes the post id to addFieldToFilter as ['eq' => $postId] (the int form was rejected); save() checks instanceof \Magento\Framework\Model\AbstractModel before calling $resource->save() instead of using a @var cast; CouldNotSaveException is rethrown only on \Exception (not \Throwable) so the cause-type contract holds. Controller/Adminhtml/Link/MultiblogPosts::execute drops the no-op ?? [] on getStoreIds() (declared as array in the interface).

Notes

  • The toolbar Block\Adminhtml\<Entity>\Edit\HyvaCmsButton classes 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_id on the saved qoliber_multiblog_author row. Pre-1.1.0 EntityImporter::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 required int $instanceId between the array and the dry-run flag; ImportWordPressCommand already 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_active was 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 applies addInstanceVisibilityFilter($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 in Qoliber\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, so SearchPlugin rendered "no posts found" during outages instead of falling back to the MySQL LIKE search. The exception now propagates and SearchPlugin's outer try/catch calls $proceed() to hand the query to the MySQL implementation. Fixes audit M-3a.
  • OpenSearchAdapter::search() no longer drops indexed posts whose publish_date is null. The range publish_date <= now filter excluded null values, while the MySQL collection accepts them via [lteq=>now] OR [null=>true]. Replaced with a bool > should clause covering both branches. Fixes audit M-3b.

Changed

  • BREAKING (internal API). SearchService::__construct() no longer accepts Psr\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

  • BlogPostDataExtractor now formats post datePublished / dateModified as UTC explicitly via new \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 stable Y-m-d\TH:i:s+00:00 regardless of where Magento is running.

Sitemap

Fixed

  • PostItemProvider and CategoryItemProvider now scope the visible-instance cache by store. Pre-1.1.0 the cache was keyed only by instance_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 to array<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.0 multiblog_post_view.xml placed the OpenGraph block in the before.body.end container, 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 to head.additional, matching the category and instance layouts that already used it. Fixes audit M-5.

Web API

Fixed

  • PublicPostRepository::getList and getById now apply the instance-visibility rule (post and parent instance must be active and store-scoped). Pre-1.1.0 only the per-post is_active / status / publish_date checks ran, so a post under a disabled instance was still served via REST. Same fix on PublicCategoryRepository. Fixes audit P0-1 (REST surfaces).

Changed

  • POST /V1/multiblog/comments is no longer declared in this module's webapi.xml. The route lived here historically but the service class belongs to Qoliber_MultiblogComments, so disabling Comments left a dangling route entry that 500'd at boot. Route moved to MultiblogComments/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 core Magento_Cms convention — 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 Hyva category/view.phtml templates were updated.
  • Swagger / REST schema generation at /rest/all/schema?services=... returned HTTP 500 (Each method must have a doc block). Magento's Webapi\Reflection\TypeProcessor requires PHPDoc @return / @param on every service-contract method — PHP 8 native return types are not enough. Added annotations to all methods on Api\Data\{Author,Category,Instance,Post,Tag}Interface (196 methods).

Changed

  • UI form requestFieldName unified with primaryFieldName (xxx_id) in all five admin forms, matching the modern Magento_Cms pattern. Admin edit URLs are now /edit/xxx_id/N; Edit / Delete / Preview / Duplicate controllers, GenericButton / DeleteButton / PreviewButton / DuplicateButton URL 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=qoliberMultiblogCommentsPublicCommentRepositoryV1 returned HTTP 500 (Each method must have a doc block). Added PHPDoc @return / @param annotations to all 22 methods on Api\Data\CommentInterface so Magento's Webapi\Reflection\TypeProcessor can build the schema.

Hyvä

Fixed

  • Category description in the Hyva category/view.phtml template was HTML-escaped, so WYSIWYG widgets, inline <style>, and page-builder directives rendered as literal text. Template now calls $block->getFilteredDescription() (provided by Qoliber\Multiblog\Block\Category\View, 1.0.1+) and emits the result with @noEscape. Added the Tailwind prose max-w-none wrapper 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_html cache was enabled (affected Post\View, Category\View, Instance\View — added getNameInLayout() to getCacheKeyInfo())
  • Author URL generation: slug-based, instance auto-resolved from AuthorInterface, added getAuthorUrlBySlug() 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.implicitlyNullable deprecations 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 BlogPosting JSON-LD with BreadcrumbList, og:image dimensions, article:tag
  • Scoped-to-instance authors (new FK + data patch)
  • is_primary flag on qoliber_multiblog_post_category for deterministic canonical
  • Async view count via qoliber_multiblog_post_view_log + hourly rollup cron + bot detection
  • Block-level cache identities + PostRelatedContent ViewModel extraction
  • Category-path single-query URL resolution
  • Router decomposed into FlatUrlResolver + CategoryUrlResolver strategies
  • Luma topmenu navigation tree caching
  • Meta-title suffix via observer

Changed

  • Composer package renamed from qoliber/module-multiblog to qoliber/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_comment for moderation context.
  • CommentRepositoryPlugin centralizing side effects (count update + approval notification) via afterSave with 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 instance allow_comments flag.

GraphQL

Added

  • Batch resolvers on nested relationships (BatchResolverInterface) — eliminates N+1 across author, category, tag, and instance traversal.
  • SortInput type with field + direction on all list queries.
  • Author resolvers no longer expose email field (information disclosure).

Changed

  • Composer package renamed from qoliber/module-multiblog-graph-ql to qoliber/multiblog-graph-ql.
  • PHP constraint bumped to 8.1–8.4.

Security

  • email stripped from MultiblogAuthor GraphQL type.

Hyvä

Added

  • Hyvä-compatible templates for instance, category, post, author, tag, and search views.
  • Hyvä navigation integration via AddBlogToHyvaNavigation plugin.
  • Tailwind-based styling for all frontend blocks, including comment thread and share buttons.
  • CommentRenderer ViewModel 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.
  • MultiblogPostProvider implements Hyva\CmsLiveviewEditor\Api\ProviderInterface and is registered as the multiblog_post content type in the Hyvä ProviderPool.
  • Schema: qoliber_multiblog_post_liveview (draft / published JSON + is_liveview_enabled flag), qoliber_multiblog_post_liveview_version_history, and qoliber_multiblog_post_liveview_tailwindcss. All three CASCADE-delete on the parent post.
  • Qoliber\MultiblogHyvaCommerce\Block\Adminhtml\Post\Edit\HyvaCmsButton adds an "Open in Hyvä CMS" toolbar button to the admin post edit form, deep-linking into liveview-editor/page/edit?type=multiblog_post&id=N.
  • Plugin\Block\Post\View::afterGetFilteredContent swaps the post body for the Hyvä CMS-rendered component tree when is_liveview_enabled is 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-cms for the editor itself.

Import

Added

  • Resumable imports via qoliber_multiblog_import_job table — 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_author rows.

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-import to qoliber/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-rss to qoliber/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-search to qoliber/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.
  • ItemProviderInterface implementation 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:titleog&#x3A;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_listing flag is set.

Changed

  • Composer package renamed from qoliber/module-multiblog-social-sharing to qoliber/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-webapi to qoliber/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_store to filter by current store)
  • Related blog posts block on product pages (catalog_product_view layout)
  • 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_listing config functional)

Fixed

  • Removed core schema.graphqls and Model/Resolver/ directory that duplicated MultiblogGraphQl resolvers without store filtering
  • Playwright test specs: replaced invalid toHaveCount({ minimum: 1 }) with proper toBeVisible() 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_featured toggle 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_next toggle)
  • 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.
  • multiblogInstance query -- fetch a single blog instance by ID with active and store checks.
  • multiblogInstanceByRoute query -- fetch a blog instance by URL route with active and store checks.
  • multiblogInstances query -- list blog instances with filter support, active and store enforcement.
  • multiblogPost query -- fetch a single post by ID with published, active, publish date, and store checks.
  • multiblogPosts query -- list posts with filter support and mandatory published/active/store enforcement.
  • multiblogCategory query -- fetch a single category by ID with active and store checks.
  • multiblogCategories query -- list categories with filter support, active and store enforcement.
  • multiblogTag query -- fetch a single tag by ID.
  • multiblogTags query -- list tags with filter support.
  • MultiblogTag GraphQL type and MultiblogTags wrapper type.
  • MultiblogTagFilterInput filter input type.
  • tag_ids field added to MultiblogPost type.
  • FilterApplier helper class for applying eq, in, and like filter 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', and publish_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 of StoreManagerInterface, 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_post table for real-time indexing
  • OpenSearch adapter with index management, bulk indexing, and search
  • SearchService with availability checks
  • Frontend plugin on PostSearch::search to route queries through OpenSearch
  • Automatic fallback to MySQL LIKE search when OpenSearch is unavailable
  • CLI command multiblog:search:reindex for 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.
  • PostResolver implementing ResolverInterface for blog post OG tags (article type, published_time, author, section).
  • CategoryResolver for blog category OG tags (website type).
  • InstanceResolver for blog instance OG tags (website type).
  • AuthorResolver for author page OG tags (profile type).
  • DI registration of all resolvers in GetTagResolver service with \Proxy lazy 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.
  • BlogVariableProvider implementing VariableProviderInterface with 15 blog post variables.
  • BlogValueResolver implementing ValueResolverInterface for resolving {{blog.*}} template placeholders.
  • DI registration for SchemaVariables, VariableExtractor, and JsonLd block.
  • Blog page type registration (post, category, instance, author) in PageType source 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
Changelog — Multiblog — Content & Knowledge Base — Extensions | qoliber Docs