the model
```
class Content extends ActiveRecord
{
	use BitmaskTrait;
	
	/**
	* Type
	* @see app\models\ContentCategory.php
	* Note: Types are handled via ContentCategory and automatically handled
	*
	const TYPE_POST = 'post';
	const TYPE_PRODUCT = 'product';
	//public const TYPE_CATALOG = 'catalog';//*/
	
	// Status
	const STATUS_DRAFT = 0;
	const STATUS_PUBLISH = 1;
	const STATUS_SOLDOUT = 3;
	
	// Purchase options
	public const PURCHASE_OPTIONS_CART = 1;// 2^0
	public const PURCHASE_OPTIONS_INQUIRE = 2;// 2^1
	public const PURCHASE_OPTIONS_BUYNOW = 4;// 2^2
	
	public static $templates = ['item', 'artwork', 'article', 'exhibition', 'artist', 'catalog'];
	
	protected static array $allowedTemplatesByType = [
		ContentCategory::TYPE_POST => ['article', 'exhibition', 'artist', 'catalog'],
		ContentCategory::TYPE_PRODUCT => ['artwork', 'exhibition', 'item', 'catalog'],
		//ContentCategory::TYPE_CATALOG => [],// no templates or as needed
	];
	
	protected static array $typeMap = [
		'post' => ContentCategory::TYPE_POST,
		'product' => ContentCategory::TYPE_PRODUCT,
		// 'catalog' => ContentCategory::TYPE_CATALOG,
	];
	
	public function scenarios(): array
	{
		$baseAttributes = [
			'type', 'template', 'category_id', 'category_title', 'status', 'title', 
			'slug', 'body', 'image_header', 'meta_description', 'meta_keywords',
			
			/**
			* Handled by TimestampBehavior
			'created_at', 'updated_at',//*/
			
			/**
			* Handled by BlameableBehavior
			'created_by', 'updated_by',//*/
		];
		
		// Add purchaseOptionsArray here because it's used virtually
		$baseAttributes[] = 'purchaseOptionsArray';
		
		return [
			/**
			* Base type-only fallback scenarios for safety
			*
			* Note: array_merge function will append the scenario rules to the base rules, but 
			* it doesn't handle potential conflicts or overwriting of rules. 
			*/
			ContentCategory::TYPE_POST => $baseAttributes,
			ContentCategory::TYPE_PRODUCT => array_merge($baseAttributes, [
				'artist', 'dimensions', 'medium', 'year', 
				'price', 'purchase_options', 'hide_price', 'quantity', 
				'product_image_one', 'product_image_two', 'product_image_three',
			]),
			//ContentCategory::TYPE_CATALOG => $baseAttributes,
			
			/**
			* Post type + template scenarios for specific validation
			*/
			'post_article' => $baseAttributes,
			'post_exhibition' => array_merge($baseAttributes, [
				'start_date', 'end_date',
			]),
			'post_catalog' => $baseAttributes,
			
			/**
			* Product type + template scenario
			*/
			'product_artwork' => array_merge($baseAttributes, [
				'artist', 'dimensions', 'medium', 'year',
				'price', 'purchase_options', 'hide_price', 'quantity', 
				'product_image_one', 'product_image_two', 'product_image_three',
			]),
			'product_item' => array_merge($baseAttributes, [
				'artist',
				'price', 'purchase_options', 'quantity', 
				'product_image_one', 'product_image_two', 'product_image_three',
			]),
			'product_catalog' => $baseAttributes,
			
			/**
			* Default
			*/
			'default' => $baseAttributes,
		];
	}
	
	public function rules(): array
	{
		/**
		* Rules
		* Rules are handled by either app\models\forms\** for user access validation, or
		* handled directly within the model and than view file skipping form model validation
		* for administration only.
		*/
		$rules = [
			/**
			* Required
			*/
			[['type', 'template', 'category_id', 'status', 'title', 'body', 'meta_description'
				/* 'created_by', 'created_at' - handled by BlameableBehavior */], 'required'],
			
			/**
			* Integers
			* Note: Price is handled as a float/string until custom validation. See below.
			*/
			[['category_id', 'status', 'year', 'quantity', /*'price'*,
				'purchase_options', Note: Empty int until declared via purchaseOptionsArray */
				'created_by', 'updated_by', 'created_at', 'updated_at'], 'integer'],
			
			/**
			* Strings
			*/
			[['body', 'template', 'meta_description', 'meta_keywords', 'category_title'], 'string'],
			[['title', 'artist', 'dimensions', 'medium', 'category_title', 'meta_keywords', 'slug'], 'string', 'max' => 255],
			[['image_header','product_image_one', 'product_image_two', 'product_image_three'], 'string', 'max' => 2000],
			['meta_description', 'string', 'max' => $this->_contentSummaryLength],
			['template', 'string', 'max' => 50],
			
			/**
			* Boolean
			*/
			['hide_price', 'boolean'],
			['hide_price', 'default', 'value' => false],
			
			/**
			* Safe
			*/
			['purchaseOptionsArray', 'safe'],// Allow purchaseOptionsArray for form binding as safe
			
			/**
			* Defaults
			*/
			['status', 'default', 'value' => self::STATUS_DRAFT],
			['status', 'in', 'range' => [self::STATUS_DRAFT, self::STATUS_PUBLISH], 
				'when' => fn($model) => $model->type === ContentCategory::TYPE_POST],
			['status', 'in', 'range' => [self::STATUS_DRAFT, self::STATUS_PUBLISH, self:: STATUS_SOLDOUT], 
				'when' => fn($model) => $model->type === ContentCategory::TYPE_PRODUCT],
			['type', 'default', 'value' => ContentCategory::TYPE_POST],
			['template', 'default', 'value' => 'article', 'when' => fn($model) => $model->type === ContentCategory::TYPE_POST],
			['template', 'default', 'value' => 'item', 'when' => fn($model) => $model->type === ContentCategory::TYPE_PRODUCT],
			['purchase_options', 'default', 'value' => self::PURCHASE_OPTIONS_CART | self::PURCHASE_OPTIONS_INQUIRE],
			
			/**
			* Custom validations 
			* A rule must specify both attribute names and validator type
			*/
			[['template'], 'validateTemplateByType'],
			[['meta_description'], 'summaryLength'],
			//[['price'], 'validateMoney', 'skipOnError' => true],// TODO
			[['purchaseOptionsArray'], 'validatePurchaseOptions'],// Handled by getter and setter
			
			/**
			* Validation messages within form
			*/
			[['price'], 'match', 'pattern' => '/^\d+(\.\d{1,2})?$/', 
				'message' => \Yii::t('app', 'The price must be a valid decimal number with up to two decimal places.')
			],
			[['price'], 'number', 'min' => 2.00, 'max' => 5000000.00, 
				'message' => \Yii::t('app', 'The price must be a valid number between $2.00 and $5,000,000.00.')
			],
			[['product_image_one', 'product_image_two', 'product_image_three'], 'url', 'skipOnEmpty' => true, 
				'message' => \Yii::t('app', 'Product Image URL is not valid.')],
			
			/**
			* Relationships
			*/
			['category_id', 'exist', 'skipOnEmpty' => false, 'skipOnError' => true, 'targetClass' => ContentCategory::class, 
				'targetAttribute' => ['category_id' => 'id'], 'message' => \Yii::t('app', 'Please select a valid category.')],
				
			['created_by', 'exist', 'skipOnError' => true, 'targetClass' => User::class, 'targetAttribute' => ['created_by' => 'id']],
			['updated_by', 'exist', 'skipOnError' => true, 'targetClass' => User::class, 'targetAttribute' => ['updated_by' => 'id']],
		];
		
		$rulesByScenario = [
			/**
			* Post + exhibition requires start and end date maybe (example)
			*/
			'post_exhibition' => [
				[['start_date', 'end_date'], 'required'],
			],
			/**
			* Product specific validations
			*/
			'product_artwork' => [
				[['artist', 'price', 'purchase_options', 'quantity', 'product_image_one'], 'required'],
			],
			'product_item' => [
				[['price', 'quantity', 'purchase_options', 'product_image_one'], 'required'],
			],
			
		];
		
		/**
		* Merge rules: rules + scenario rules if any
		*
		* Note: array_merge function will append the scenario rules to the base rules, but it 
		* doesn't handle potential conflicts or overwriting of rules. 
		*/
		$mergedRules = array_merge($rules, $rulesByScenario[$this->scenario] ?? []);
		//\yii\helpers\VarDumper::dump($mergedRules, 10, true);// TODO Debug scenarios tool
		
		return $mergedRules;
	}
	
	public function validateTemplateByType($attribute, $params = []): void
	{
		// Can't validate without type set
		if (!$this->type)
		{
			return;
		}
		
		// @see below for getAllowedTemplates();
		$allowedTemplates = $this->getAllowedTemplates();
		
		if ($this->$attribute !== null && !in_array($this->$attribute, $allowedTemplates, true))
		{
			$this->addError(
				$attribute,
				\Yii::t('app', 'The selected template "{template}" is not allowed for this category.', [
					'template' => $this->$attribute
				])
			);
			
			return;
		}
	}
	
	public function beforeValidate(): bool
	{
		if (!parent::beforeValidate())
		{
			return false;
		}
		
		// ...custom code here...
		
		/**
		* Set scenario automatically based on `type` and `template` to ensure validation 
		* rules apply correctly.
		*/
		if ($this->type && $this->template)
		{
			// Construct the combined scenario name by concatenating type and template with an underscore
			// For example: 'product_artwork', 'post_exhibition'
			$combinedScenario = $this->type . '_' . $this->template;
			
			// Check if scenario exists: use combined, else fallback to type or 'default'
			if (isset($this->scenarios()[$combinedScenario]))
			{
				// If the combined scenario exists, set it as the current scenario
				$this->setScenario($combinedScenario);
			}
			// If combined scenario does not exist, check if a scenario matching just the type exists
			elseif (isset($this->scenarios()[$this->type]))
			{
				// Set the scenario to the type-only scenario (e.g., 'product' or 'post')
				$this->setScenario($this->type);
			}
			// If neither combined nor type-only scenario exists,
			else
			{
				// Fallback to a default scenario to prevent validation errors
				$this->setScenario('default');
			}
		}
		// If only 'type' is set (no template)
		elseif ($this->type)
		{
			// Set the scenario to the type scenario
			$this->setScenario($this->type);
		}
		// If neither 'type' nor 'template' is set,
		else
		{
			// Set a generic default scenario so validation can still proceed without errors
			$this->setScenario('default');
		}
		
		return true;
	}
	
	public function getAllowedTemplates(): array
	{
		// Check if the category relation is loaded and has allowed templates
		if ($this->category && $this->category->getAllowedTemplates())
		{
			return $this->category->getAllowedTemplates();
		}
		
		// fallback to global allowed templates by 'type'
		return self::$allowedTemplatesByType[$this->type] ?? [];
	}
	
	/**
	* Returns the allowed templates for a given content type statically.
	* @param int|string $type
	* @return array
	*
	* Use this method when you want the list of allowed templates for a specific
	* content type without needing an instance of Content.
	* Example usage:
	* 
	*	print_r(Content::getAllowedTemplatesByType(ContentCategory::TYPE_POST));
	* 	// Output: ['article', 'exhibition', 'artist', 'catalog']
	*
	*	print_r(Content::getAllowedTemplatesByType('post'));
	*	// Output: ['article', 'exhibition', 'artist', 'catalog']
	* 
	*	print_r(Content::getAllowedTemplatesByType('PRODUCT'));
	* 	// Output: ['item', 'artwork', 'exhibition', 'catalog']
	*/
	public static function getAllowedTemplatesByType(int|string $type): array
	{
		if (is_string($type))
		{
			$type = strtolower($type);// normalize
			
			if (isset(self::$typeMap[$type]))
			{
				$type = self::$typeMap[$type];
			}
			else
			{
				// Unknown string type, return empty or error as you prefer
				return [];
			}
		}
		
		return self::$allowedTemplatesByType[$type] ?? [];
	}
	
	/**
	* Returns the entire mapping of content types to their allowed templates.
	* @return array
	*
	* Use when you want all allowed templates grouped by type.
	* Example usage:
	* 
	* 	$allTemplates = Content::getAllAllowedTemplates();
	*
	* Result:
	* 	[
	*		'post' => ['article', 'exhibition', 'artist', 'catalog'],
	*		'product' => ['item', 'artwork', 'exhibition', 'catalog'],
	*	]
	*/
	public static function getAllAllowedTemplates(): array
	{
		return self::$allowedTemplatesByType;
	}
	
	/**
	* Returns a key-value list mapping template keys to their human-friendly labels.
	* @return array Associative array of template keys and their labels.
	*
	* This is useful for generating dropdown lists, checkbox lists, or for displaying
	* template labels in views.
	*
	* Example usage:
	* 
	* 	$templateOptions = Content::getTemplateList();
	*
	* Result:
	*	[
	* 		'item' => 'Item',
	* 		'artwork' => 'Artwork',
	* 		'article' => 'Article',
	* 		...
	* 	]
	*/
	public static function getTemplateList(): array
	{
		$templates = self::$templates;
		
		// Use keys and values the same or map them to friendly labels
		$labels = [
			'item' => \Yii::t('app', 'Item'),
			'artwork' => \Yii::t('app', 'Artwork'),
			'article' => \Yii::t('app', 'Article'),
			'exhibition' => \Yii::t('app', 'Exhibition'),
			'artist' => \Yii::t('app', 'Artist'),
			'catalog' => \Yii::t('app', 'Catalog'),
		];
		
		$list = [];
		
		foreach ($templates as $template)
		{
			$list[$template] = $labels[$template] ?? ucfirst($template);
		}
		
		return $list;
	}
```

the controller
```
		/**
		* Setup Category and prepare variables for JS
		*/
		$data = $this->getCategoryOptions(true);// need categoryTypeMap for update
		$allowedTemplatesJson = Json::encode($data['categoryTemplatesMap'] ?? []);
		$categoryTypeMapJson = Json::encode($data['categoryTypeMap'] ?? []);
		$currentTemplateJs = Json::encode('');
		$currentTypeJs = Json::encode(null);
		
		// Render Jquery
		$this->renderJS($allowedTemplatesJson, $categoryTypeMapJson, $currentTemplateJs, $currentTypeJs);
		
		return $this->render('create', [
			'model' => $model,
			'allowedTemplatesByType' => Content::getAllAllowedTemplates(),
			'categoryOptions' => $data['categoryOptions'],
			'categoryTypeMap' => $data['categoryTypeMap'],
			'contentSummaryLength' => $this->_contentSummaryLength,
			'mediaBlogImageRecSize' => $this->_mediaBlogImageRecSize,
			'mediaProductImageRecSize' => $this->_mediaProductImageRecSize,
		]);
		
		/**
	* Get category dropdown options and optionally the categoryTypeMap (categoryId => type).
	* @param bool $includeTypeMap Whether to include 'categoryTypeMap' in the return
	* @return array
	*/
	private function getCategoryOptions(bool $includeTypeMap = true): array
	{
		$categories = ContentCategory::find()->all();
		
		if (!$categories)
		{
			return [
				'categoryOptions' => [],
				'categoryTypeMap' => $includeTypeMap ? [] : null,
				'categoryTemplatesMap' => $includeTypeMap ? [] : null,
			];
		}
		
		// Build hierarchical and flat category list
		$hierarchicalCategories = ContentCategory::structureCategories($categories);
		$flatCategories = ContentCategory::flattenCategoriesDropdown($hierarchicalCategories);
		
		if (!is_array($flatCategories))
		{
			$flatCategories = [];
		}
		
		// Map integer type IDs to string keys expected in JS/toggle logic
		$typeLabels = [
			ContentCategory::TYPE_POST => 'post',
			ContentCategory::TYPE_PRODUCT => 'product',
			// Add other type mappings as needed
		];
		
		$categoryTypeMap = null;
		$categoryTemplatesMap = null;
		
		if ($includeTypeMap)
		{
			// Build categoryTypeMap and categoryTemplatesMap with type strings (not numeric IDs)
			$categoryTypeMap = [];
			$categoryTemplatesMap = [];
			
			foreach ($categories as $category)
			{
				$categoryTypeMap[$category->id] = $typeLabels[$category->type] ?? null;
			
				$allowedTemplates = $category->getAllowedTemplates();
				
				if (empty($allowedTemplates))
				{
					// fallback to global allowed templates by type
					$typeKey = $categoryTypeMap[$category->id] ?? '';
					$allowedTemplates = Content::getAllowedTemplatesByType($typeKey);
				}
				
				$categoryTemplatesMap[$category->id] = $allowedTemplates;
			}
		}
		
		return [
			'categoryOptions' => $flatCategories,
			'categoryTypeMap' => $categoryTypeMap,
			'categoryTemplatesMap' => $categoryTemplatesMap,
		];
	}
	
	/**
	* Handle Javascript and Caching
	*/
	private function renderJS(
		string $allowedTemplatesJson,
		string $categoryTypeMapJson,
		string $currentTemplateJs,
		string $currentTypeJs
	): void
	{
		$uniqueId = uniqid('', false);// Create 13-char unique string based on time
		$cacheKey = $this->_cacheKeyPrefix . $uniqueId . '_js_block_';
		$jsBlock = $this->getCachedJsBlock($cacheKey, function () use (
			$allowedTemplatesJson,
			$categoryTypeMapJson,
			$currentTemplateJs,
			$currentTypeJs
		)
		{
			/*
			* JavaScript
			* CSS Classes to control field visibility:
			* - Apply the class `.field-post` to all form fields that should be shown for content type "post".
			* - Apply the class `.field-product` to all form fields that should be shown for content type "product".
			* - Apply the class `.field-exhibition` to fields shown only when the template is "exhibition" (usually for post type).
			*
			* Examples:
			* Shows 'start_date' field only if content type is 'post' and template is 'exhibition'"
			*
			* 	<?= $form->field($model, 'start_date', ['options' => ['class' => 'field-post field-exhibition']]) ?>
			*
			* Shows 'artist' field only if content type is 'product': 
			*
			* 	<?= $form->field($model, 'artist', ['options' => ['class' => 'field-product']]) ?>
			*
			* This approach allows fine-grained control of form field visibility without relying on grouping 
			* fields inside containers, enabling more flexible layouts.
			*/
			return <<<js
			(function(\$)
			{
				\$(function()
				{
					/* Map: category ID => array of allowed templates for that category */
					var categoryTemplatesMap = $allowedTemplatesJson;
					/* Map: category ID => content type string ('post', 'product', etc.) */
					var categoryTypeMap = $categoryTypeMapJson;
					
					var \$category = \$('#content-category');
					var \$template = \$('#content-template');
					
					function getTypeForCategory(catId)
					{
						/* Convert catId to string for comparison because JSON keys are strings */
						return categoryTypeMap.hasOwnProperty(String(catId)) ? categoryTypeMap[String(catId)] : null;
					}
					
					function updateTemplateOptions(catId)
					{
						var templates = categoryTemplatesMap[catId] || [];
						\$template.empty();
						
						if (templates.length === 0)
						{
							\$template.append(\$('<option>').val('').text('Select a template'));
							\$template.prop('disabled', true);
						}
						else
						{
							\$.each(templates, function(i, val)
							{
								\$template.append(\$('<option>').val(val).text(val));
							});
							
							\$template.prop('disabled', false);
						}
						
						var currentTemplate = $currentTemplateJs;
						
						/* Array.prototype.includes is ES6+ */
						if (\$.inArray(currentTemplate, templates) !== -1)
						{
							\$template.val(currentTemplate);
						}
						else
						{
							\$template.val(templates[0] || '');
						}
					}
					
					function toggleFields(type, template)
					{
						console.log('toggleFields called with:', type, template);
						
						/* Hide all post and product related fields */
						\$('.field-post, .field-exhibition, .field-product').hide();
						
						if (type === 'post')
						{
							\$('.field-post').show();
							
							if (template === 'exhibition')
							{
								\$('.field-exhibition').show();
							}
						}
						else if (type === 'product')
						{
							\$('.field-product').show();
						}
					}
					
					var initialCategoryId = \$category.val();
					var initialType = getTypeForCategory(initialCategoryId) || $currentTypeJs;
					
					if (!categoryTemplatesMap || Object.keys(categoryTemplatesMap).length === 0)
					{
						/* No categories/templates loaded - disable template dropdown and hide fields */
						\$template.empty().append(\$('<option>').val('').text('Select a category first')).prop('disabled', true);
						toggleFields(null, null);
					}
					else
					{
						updateTemplateOptions(initialType);
						var initialTemplate = \$template.val();
						toggleFields(initialType, initialTemplate);
					}
					
					\$category.on('change', function()
					{
						var catId = \$(this).val();
						
						if (!catId || !getTypeForCategory(catId))
						{
							\$template.empty().append(\$('<option>').val('').text('Select a category first')).prop('disabled', true);
							toggleFields(null, null);
							
							return;
						}
						
						var type = getTypeForCategory(catId);
						updateTemplateOptions(catId);
						var newTemplate = \$template.val();
						toggleFields(type, newTemplate);
					});
					
					\$template.on('change', function()
					{
						var template = \$(this).val();
						var type = getTypeForCategory(\$category.val()) || $currentTypeJs;
						toggleFields(type, template);
					});
					
				});
			})(jQuery);
			js;
		});
		
		$this->view->registerJs($jsBlock, View::POS_END);
	}
```

the _form view code

```
declare(strict_types=1);

use yii\bootstrap5\Html;
use yii\bootstrap5\ActiveForm;
use yii\widgets\DetailView;
use yii\helpers\ArrayHelper;
use yii\helpers\Url;

use app\models\Content;
use app\models\search\ContentSearch;
use app\widgets\TrumbowygJQry;
use app\helpers\ViewHelper;

/**
* Setup CategoryTypeMap + Template Engine
*
* Ensure $categoryTypeMap is an array, even if not provided
* This map links category IDs to their corresponding content types (e.g., 'post', 'product')
*/
$categoryTypeMap = $categoryTypeMap ?? [];
// Get the currently selected category ID from the model, or null if none selected
$currentCategoryId = $model->category_id ?? null;
// Initialize variable to hold the content type related to the selected category
$currentType = null;

// If there is a selected category and it exists in the categoryTypeMap,
// set the currentType to the mapped content type of that category
if ($currentCategoryId && isset($categoryTypeMap[$currentCategoryId]))
{
	$currentType = $categoryTypeMap[$currentCategoryId];
}
// Otherwise, if no category is selected or it is not in the map,
// fall back to using the content type directly from the model (if available)
else
{
	$currentType = $model->type ?? null;
}

// Based on the determined content type, fetch the allowed templates for that type
// If currentType is null or not found in allowedTemplatesByType, set templates to empty array
$templatesForCurrentType = $currentType && isset($allowedTemplatesByType[$currentType]) 
	? $allowedTemplatesByType[$currentType]
	: [];
?>


<?php
$form = ActiveForm::begin([
	'id' => 'content-form',
	'method' => 'post',
	'options' => ['class' => 'active-form mx-auto'],
]);
/*
<?= $form->field($model, 'category_id')->textInput(); ?>
<?= $form->field($model, 'template')->dropDownList(ContentSearch::getTemplateList(), ['prompt' => 'Select Template']) ?>
<?= $form->field($model, 'category_title')->textInput(['maxlength' => true]); ?>
<?= $form->field($model, 'status')->textInput(); ?>
<?= $form->field($model, 'title')->textInput(['maxlength' => true]); ?>
<?= $form->field($model, 'slug')->textInput(['maxlength' => true]); ?>
<?= $form->field($model, 'body')->textarea(['rows' => 6]); ?>
<?= $form->field($model, 'image_header')->textInput(['maxlength' => true]); ?>
<?= $form->field($model, 'meta_description')->textInput(['maxlength' => true]); ?>
<?= $form->field($model, 'meta_keywords')->textInput(['maxlength' => true]); ?>

<?= $form->field($model, 'artist')->textInput(); ?>
<?= $form->field($model, 'dimensions')->textInput(); ?>
<?= $form->field($model, 'medium')->textInput(); ?>
<?= $form->field($model, 'year')->textInput(); ?>
<?= $form->field($model, 'price')->textInput(); ?>
<?= $form->field($model, 'purchaseOptionsArray', [])->checkboxList([]); ?>
<?= $form->field($model, 'hide_price')->checkbox(); ?>
<?= $form->field($model, 'quantity')->textInput(); ?>
<?= $form->field($model, 'product_image_one')->textInput(['maxlength' => true]); ?>
<?= $form->field($model, 'product_image_two')->textInput(['maxlength' => true]); ?>
<?= $form->field($model, 'product_image_three')->textInput(['maxlength' => true]); ?>

<?= $form->field($model, 'created_by')->textInput(); ?>
<?= $form->field($model, 'updated_by')->textInput(); ?>
<?= $form->field($model, 'created_at')->textInput(); ?>
<?= $form->field($model, 'updated_at')->textInput(); ?>
*/ ?>

<ul class="nav justify-content-center mb-3">
<li class="nav-item ms-3"><?= Html::a(\Yii::t('app', 'view media'), ['media/index'], ['class' => 'nav-link']); ?></li>
</ul>
```
The issue is when updating already existing data, the template scenario should be already set but when saving data does not seem to be saving properly

```
Thanks for sharing the detailed model, controller, and form code. From what you've described and the code setup, the key symptom is that when updating existing Content data, the template scenario is not saving or applying properly, likely due to the model's scenario or attribute values not being set or populated as expected before validation and saving.

I'll go over the typical issues and root causes in similar scenarios and suggest actionable debugging and fixes.
Problem Understanding and Context

    Scenario auto-set in beforeValidate(): The model beforeValidate() sets the scenario based on the combined type and template values.
    On update, the model should already have a type and template loaded from the DB, so the correct scenario should be selected automatically.
    However, when saving, seems like the template or type value is lost or not set properly, causing the wrong scenario or validation issues.
    The controller sends some JS that uses categoryTypeMap and categoryTemplatesMap to populate template dropdown dynamically based on category.
    The form dropdown for template is being populated with all templates from ContentSearch::getTemplateList(), which might not be filtered by actual allowed templates for the type/category.
    You mention the scenario should be set already when updating, but saving doesn't save correctly (likely validations fail or scenario is not correct).

What I Suspect
1. The template dropdown options do not match the allowed templates on update

    Because in the form, the template dropdown uses ContentSearch::getTemplateList() unfiltered by actual allowed templates of the current type/category.
    This can cause a mismatch if the old template value is not in the dropdown or not in allowed templates, causing scenario mismatch or validation errors.

2. The automatic scenario setting in beforeValidate() depends on type and template being set correctly before validation.

    However, if type and template are not loaded or overridden by POST data incorrectly, scenario becomes 'default' or a wrong one.
    Particularly: if template is empty or not among allowed for that type, validation fails.

3. The JS that updates templates dropdown based on category might cause the form dropdown to lose its old value or not display it correctly on update.

    Your JS sets template options based on the selected category, but initial JS code:

var currentTemplate = <?= $currentTemplateJs ?>;

which in your controller is set as:

    $currentTemplateJs = Json::encode('');

    So you are passing empty string '' as current template to JS, meaning JS will reset the template dropdown to the first option available.
    Thus, user's original $model->template value is not used to preset the template dropdown on form load.

Steps to Fix and Improve
Step 1: Pass actual current template value from the model to the controller and JS

In your controller action (update or create), when you render:

Replace

$currentTemplateJs = Json::encode('');
$currentTypeJs = Json::encode(null);

with

$currentTemplateJs = Json::encode($model->template ?? '');
$currentTypeJs = Json::encode($model->type ?? null);

    This way, your JS on page load knows which template is currently selected and won't reset it to an empty value.
    This alone might let the JS properly select the correct current template option.

Update the related part:

// Render Jquery
$this->renderJS($allowedTemplatesJson, $categoryTypeMapJson, $currentTemplateJs, $currentTypeJs);

Step 2: Fix the form dropdown for template to only show allowed templates of current type/category

In your _form view, you do:

<?= $form->field($model, 'template')->dropDownList(ContentSearch::getTemplateList(), ['prompt' => 'Select Template']) ?>

but ContentSearch::getTemplateList() returns all templates possible.

You ideally want to limit it to $templatesForCurrentType:

<?= $form->field($model, 'template')->dropDownList(
    \yii\helpers\ArrayHelper::map($templatesForCurrentType, function($t){return $t;}, function($t){ return ucfirst($t); }),
    ['prompt' => 'Select Template']
) ?>

or use the helper Content::getTemplateList() filtered by $templatesForCurrentType keys:

Example:

<?php
$allTemplates = Content::getTemplateList();
$allowedTemplatesListForType = array_intersect_key($allTemplates, array_flip($templatesForCurrentType));
?>
<?= $form->field($model, 'template')->dropDownList($allowedTemplatesListForType, ['prompt' => 'Select Template']) ?>

    This ensures user can only select valid templates for the current content type, which matches the scenario logic and validations.
    On update, it will also include the currently saved template.

Step 3: Ensure type and template are loaded from DB and assigned before validation

Generally $model->load($postData) will assign these, but make sure when loading model you also set scenario or the beforeValidate() picks up the type/template from the loaded model and not overridden.

In update action, something like:

$model = Content::findOne($id);
if (!$model) throw new NotFoundHttpException();

if ($model->load(Yii::$app->request->post())) {
    // type and template should be loaded automatically
    // but you can force scenario explicitly here if needed
    $model->setScenario($model->type . '_' . $model->template);
    
    if ($model->save()) {
        // success
        return $this->redirect(...);
    }
}

Although your beforeValidate() sets scenario automatically, sometimes explicitly setting it after load helps avoid conflicts.
Step 4: Confirm JavaScript on form load calls updateTemplateOptions(initialCategoryId) with category ID instead of type

Your JS function:

updateTemplateOptions(initialType);

calls updateTemplateOptions with initialType, which expects a category ID.

But $allowedTemplatesJson and $categoryTypeMapJson keys are category IDs, so you need to call it with category ID, i.e.:

updateTemplateOptions(initialCategoryId);

instead of:

updateTemplateOptions(initialType);

Fix the code block:

var initialCategoryId = $category.val();
var initialType = getTypeForCategory(initialCategoryId) || $currentTypeJs;

if (!categoryTemplatesMap || Object.keys(categoryTemplatesMap).length === 0) {
    // ...
} else {
    updateTemplateOptions(initialCategoryId);
    var initialTemplate = $template.val();
    toggleFields(initialType, initialTemplate);
}

The mismatch here causes templates dropdown to be built empty or wrong on initial load.
Step 5: Double-check that your purchaseOptionsArray virtual attribute is properly loaded and saved (if it interferes)

If you have a virtual property and validation around it, make sure it does not cause validation or saving failure and that its getter/setter properly mutate the main DB attribute.
Summary of Fixes Required
Fix 	Reason
Pass $model->template and $model->type to JS as $currentTemplateJs and $currentTypeJs 	So JS selects the right template and type on form load.
Filter template dropdown in form to only allowed templates for type/category 	Ensures user cannot select invalid templates.
Fix updateTemplateOptions JS call to use category ID instead of type 	Because JS maps are keyed by category ID.
Optionally explicitly set scenario after $model->load() in controller 	To avoid scenario mismatch on save.
Verify virtual attributes like purchaseOptionsArray do not cause save issues 	To ensure overall validation passes.
Example adjusted critical controller snippet for update:

public function actionUpdate($id)
{
    $model = Content::findOne($id);
    if (!$model) {
        throw new NotFoundHttpException();
    }

    $data = $this->getCategoryOptions(true);
    $allowedTemplatesJson = Json::encode($data['categoryTemplatesMap'] ?? []);
    $categoryTypeMapJson = Json::encode($data['categoryTypeMap'] ?? []);
    // Pass actual current values to JS for initial template and type
    $currentTemplateJs = Json::encode($model->template);
    $currentTypeJs = Json::encode($model->type);

    $this->renderJS($allowedTemplatesJson, $categoryTypeMapJson, $currentTemplateJs, $currentTypeJs);

    if ($model->load(Yii::$app->request->post())) {
        // Optionally force scenario
        $combinedScenario = $model->type . '_' . $model->template;
        $scenarios = $model->scenarios();
        if (isset($scenarios[$combinedScenario])) {
            $model->setScenario($combinedScenario);
        } elseif (isset($scenarios[$model->type])) {
            $model->setScenario($model->type);
        } else {
            $model->setScenario('default');
        }

        if ($model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        }
    }

    return $this->render('update', [
        'model' => $model,
        'allowedTemplatesByType' => Content::getAllAllowedTemplates(),
        'categoryOptions' => $data['categoryOptions'],
        'categoryTypeMap' => $data['categoryTypeMap'],
        // etc...
    ]);
}

Example adjusted JS snippet inside renderJS:

var initialCategoryId = $category.val();
var initialType = getTypeForCategory(initialCategoryId) || $currentTypeJs;

if (!categoryTemplatesMap || Object.keys(categoryTemplatesMap).length === 0) {
    $template.empty().append($('<option>').val('').text('Select a category first')).prop('disabled', true);
    toggleFields(null, null);
} else {
    updateTemplateOptions(initialCategoryId);
    var initialTemplate = $template.val();
    toggleFields(initialType, initialTemplate);
}

Example adjusted form template dropdown:

<?php
$allTemplates = Content::getTemplateList();
$allowedTemplatesListForType = array_intersect_key($allTemplates, array_flip($templatesForCurrentType));
?>
<?= $form->field($model, 'template')->dropDownList($allowedTemplatesListForType, ['prompt' => 'Select Template']) ?>

Final notes

    Make sure the category_id selection controls and the template dropdown are in sync and that on update the form initializes properly.
    The JS used on frontend must initialize dropdown options using category_id keys, not type strings.
    Always pass existing model values from your controller to JS and view to pre-populate correctly.
    Scenario switching in beforeValidate helps at validation time, but form inputs and initial dropdown values must be consistent to avoid flickers or mismatches.

If you follow these steps, you should see that updates load the correct scenario automatically, and saving should work with validation for the right combination of type and template.

If you want, I can help you refactor or write unit tests to ensure this logic works correctly on update as well. Just ask!
```

still facing the same issue. When i added this
```

```
	$model = $this->findModel($id);
		
	$session = \Yii::$app->session;
	$request = \Yii::$app->request;

	public function actionUpdate(int $id): Response|string
	{
		if (($request->isPost) && ($model->load($request->post())))
		{
			$typeKey = $model->getTypeKey() ?: 'default';
			$combinedScenario = $typeKey . '_' . $model->template;
			$scenarios = $model->scenarios();
			
			if (isset($scenarios[$combinedScenario]))
			{
				$model->setScenario($combinedScenario);
			}
			elseif (isset($scenarios[$typeKey]))
			{
				$model->setScenario($typeKey);
			}
			else
			{
				$model->setScenario('default');
			}
			
			LogHelper::debug("Content Update Scenario: " . $model->getScenario(), [
				'combinedScenario' => $combinedScenario,
				'currentScenario' => $model->getScenario(),
				'allScenarios' => array_keys($scenarios),
			]);
```
this is the output: 2_artwork

when im trying to save price, it does not and i think im stuck on default. The debug output:

    "combinedScenario": "product_artwork",
    "currentScenario": "product_artwork",
    "allScenarios": [
        1,
        2,
        "post_article",
        "post_exhibition",
        "post_catalog",
        "product_artwork",
        "product_item",
        "product_catalog",
        "default"
    ]