KS Cases
Cases as a content type, with compatible archives, templates, listings, and a reversible migration from the legacy "cases" category.
Install
This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.
Install straight from GitHub with WP-CLI:
wp plugin install https://github.com/kostyasorokin/wp-ks-cases/archive/refs/heads/main.zipCases as a dedicated WordPress content type. The plugin registers ks_case
with separate case categories and tags, keeps individual case addresses at
/cases/<name>/, and provides an archive, a single template, card partial, theme
API, shortcode, and service CTA.
It also includes an explicit, reversible importer for sites that previously
stored cases as ordinary posts in the cases category. Activation never moves
content by itself.
Requirements
PHP 8.5 and WordPress 6.7 or newer.
Install and adopt existing cases
- Put the directory at
wp-content/plugins/ks-casesand activate KS Cases. - Open Cases → Import.
- Review the posts found directly in the
casescategory. - Click Import these cases.
While legacy posts remain, the original category continues to own its archive,
so activation cannot replace the live listing with an empty CPT query. Once the
import succeeds, cases use /cases/ for the listing and
/cases/<name>/ for individual entries. The former /category/cases/ address
permanently redirects to /cases/.
The importer changes only wp_posts.post_type, leaving each post ID and its
content, author, publication date, featured image, comments, custom fields, and
Yoast data in place. It copies meaningful categories and all tags into the
case-only vocabularies, records the original term IDs for rollback, then removes
the case relationships from the blog taxonomies. Structural blog and cases
categories are intentionally not copied.
Before conversion the importer records the old permalink. Old addresses that no longer match the new structure answer with a permanent redirect. When TranslatePress is present, redirects also recognise its known language prefixes.
The same screen can restore everything imported by this tool to ordinary posts, including the exact original blog category and tag relationships. Cases created directly in the new content type are never included in that rollback.
Settings
Cases → Settings stores these options:
| Option | Default | Purpose |
|---|---|---|
ksCasesCaseSlug |
cases |
Address of one case |
ksCasesArchiveSlug |
cases |
Address of the listing |
ksCasesSingularName |
translated “Case” | Admin wording |
ksCasesPluralName |
translated “Cases” | Admin menu wording |
ksCasesArchiveEyebrow |
translated “Our results” | Line above the archive title |
ksCasesArchiveTitle |
plural name | Archive heading |
ksCasesArchiveSubtitle |
translated default | Archive introduction |
ksCasesPerPage |
12 |
Archive page size |
ksCasesMenuIcon |
dashicons-awards |
Admin menu icon |
Address changes schedule one rewrite flush on the next request. Existing URLs are not guessed when settings change; add redirects before changing a live address.
Categories and tags
Cases never share the blog's built-in category and post_tag taxonomies:
ks_case_category— hierarchical case categories, with public term URLs at/cases/category/<name>/;ks_case_tag— case tags, with public term URLs at/cases/tag/<name>/.
Both appear under the Cases admin menu, in the case list, block editor, REST API, and public templates. Migrated terms are separate WordPress term records even when their names and slugs match blog terms.
The public archive automatically shows every non-empty case category below its introduction and a complete case-tag cloud after the card listing. The active category is marked for visitors and assistive technology.
Service block
The case edit screen includes a Service block metabox with a heading, a
multiline subtitle, and a multiple select populated by ks_service posts. When
at least one public service is selected, the plugin appends one custom CTA after
the editorial content. Selected services appear as normal links whose labels are
their complete post titles; no Gutenberg block markup is stored or rendered.
The heading, subtitle, and ordered service IDs are protected, revisioned post meta. If KS Services is inactive, the selector explains the dependency. If a selected service stops being public, its link is omitted on the front end.
Case team
With KS Team active, the case edit screen shows a Case team checklist. Select everyone who worked on the case and save it. The single case page then shows their names, current job titles, and featured photos from KS Team. Each person links to their profile when KS Team's public member pages are enabled. Unpublished members are omitted from the public case page.
The selected member IDs are protected, revisioned post meta. Deactivating KS Team hides the selector and public cards while preserving the saved selection.
Shortcode and theme API
[ks_cases] lists all published cases newest first. Optional attributes:
[ks_cases limit="6" columns="3"]
[ks_cases category="military-affairs" limit="4" columns="2"]
category is a ks_case_category slug. columns is clamped to 1–4 and
limit="0" means all cases. An empty query returns an empty string.
Themes can use KonstantinSorokin\Cases\Helpers\Cases:
Cases::all( string $category = '', int $limit = 0 )Cases::card( int $postId )Cases::archiveUrl()Cases::settings()Cases::categories( int $postId )Cases::tags( int $postId )Cases::categoryLinks( int $postId, string $separator = ', ' )Cases::tagLinks( int $postId, string $separator = ', ' )Cases::serviceBlock( int $postId )Cases::teamMembers( int $postId )
Templates and extension points
A classic theme can override the plugin fallbacks with:
archive-ks_case.phpsingle-ks_case.phptaxonomy-ks_case_category.phptaxonomy-ks_case_tag.phptaxonomy-ks_case.phpks-cases/case-card.phpks-cases/service-block.phpks-cases/team-members.php
The Kerimov Partners child theme already ships the archive and single templates, preserving the current site design.
Filters:
ksCasesPostTypeArgs— allregister_post_type()arguments.ksCasesCategoryTaxonomyArgs— all case category taxonomy arguments.ksCasesTagTaxonomyArgs— all case tag taxonomy arguments.ksCasesPerPage— archive page size after reading the setting.ksCasesFallbackTemplate— plugin fallback path and requested theme file.
The stored post type can be overridden as KS_CASES_POST_TYPE in
wp-config.php, but only before the first case exists. Changing it later does
not move stored rows. Taxonomy constants follow the same rule.
Development
The shipped Composer autoloader contains no third-party packages. Development tools live separately so they cannot leak into production:
composer tools
composer check
The check runs WordPress Coding Standards, PHPStan level 8, and the unit suite.