<?php
/*
 * Copyright (c) 2026, Tribal Limited
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of Zenario, Tribal Limited nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL TRIBAL LTD BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
if (!defined('NOT_ACCESSED_DIRECTLY')) exit('This file may not be directly accessed');



//Code for converting table data, after the more drastic database structure changes




//Some old updates. Not currently needed but I'm just keeping the code
//here so I can easily find it if I ever need to re-issue them again.

/*
//Scan anything related to a Content Item and sync the inline_images table properly
if (ze\dbAdm::needRevision(30731)) {
	
	$result = ze\row::query('content_items', ['id', 'type', 'visitor_version', 'admin_version'], [], ['type', 'id']);
	while ($row = ze\sql::fetchAssoc($result)) {
		
		if ($row['visitor_version']) {
			ze\contentAdm::updateContentItemCache($row['id'], $row['type'], $row['visitor_version']);
		}
		if ($row['admin_version'] && $row['admin_version'] != $row['visitor_version']) {
			ze\contentAdm::updateContentItemCache($row['id'], $row['type'], $row['admin_version']);
		}
	}
	
	ze\dbAdm::revision(30731);
}

//Update Organizer's image thumbnails
if (ze\dbAdm::needRevision(31200)) {
	$docstoreDir = ze::setting('docstore_dir');
	
	foreach ([
		['thumbnail_180x130_data', 'thumbnail_180x130_width', 'thumbnail_180x130_height', 180, 130],
		['thumbnail_64x64_data', 'thumbnail_64x64_width', 'thumbnail_64x64_height', 64, 64],
		['thumbnail_24x23_data', 'thumbnail_24x23_width', 'thumbnail_24x23_height', 24, 23]
	] as $c) {

		$sql = "
			SELECT id, location, path, filename, data, mime_type, width, height
			FROM ". DB_PREFIX. "files
			WHERE mime_type IN ('image/gif', 'image/png', 'image/jpeg', 'image/webp')
			  AND width != 0
			  AND height != 0
			  AND (`". ze\escape::sql($c[0]). "` IS NULL
				OR `". ze\escape::sql($c[1]). "` > ". (int) $c[3]. "
				OR `". ze\escape::sql($c[2]). "` > ". (int) $c[4]. "
				OR (	width > ". (int) $c[3]. "
					AND height > ". (int) $c[4]. "
					AND `". ze\escape::sql($c[1]). "` < ". (int) $c[3]. "
					AND `". ze\escape::sql($c[2]). "` < ". (int) $c[4]. "
			))";
		$result = ze\sql::select($sql);

		while($img = ze\sql::fetchAssoc($result)) {
			if ($img['location'] == 'docstore') {
				if ($docstoreDir && is_file($docstoreDir. '/'. $img['path'])) {
					$img['data'] = file_get_contents($docstoreDir. '/'. $img['path']. '/'. $img['filename']);
				} else {
					continue;
				}
			}
	
			ze\image::resize($img['data'], $img['mime_type'], $img['width'], $img['height'], $c[3], $c[4]);
			$img['data'] = "
				UPDATE ". DB_PREFIX. "files SET
					`". ze\escape::sql($c[0]). "` = '". ze\escape::sql($img['data']). "',
					`". ze\escape::sql($c[1]). "` = ". (int) $img['width']. ",
					`". ze\escape::sql($c[2]). "` = ". (int) $img['height']. "
				WHERE id = ". (int) $img['id'];
			ze\sql::update($img['data']);
			unset($img);
		}
	}
	
	ze\dbAdm::revision(31200);
}
*/


//There was a conductor bug where if you re-ordered the slides, the slide numbers got updated on the
//slides themselves but not on the nests going between them.
//Fix any bad data where this has happened.
if (ze\dbAdm::needRevision(52200)) {
	
	$result = ze\row::query('nested_plugins', ['instance_id', 'slide_num', 'states'], ['is_slide' => 1, 'states' => ['!' => '']]);
	foreach ($result as $slide) {
		foreach (ze\ray::explodeAndTrim($slide['states']) as $state) {
			$key = ['instance_id' => $slide['instance_id'], 'from_state' => $state];
			ze\row::update('nested_paths', ['slide_num' => $slide['slide_num']], $key);
		}
	}
	
	ze\dbAdm::revision(52200);
}

//Starting with this release, all Multiple Image Container files will be stored in the docstore.
//Their "usage" value will be "mic".
//Any existing image that is used for other purposes (not just MIC plugins) will be duplicated with the new "usage" value,
//and all plugins where it was used will instead use the new file.
if (ze\dbAdm::needRevision(52205)) {
	
	$module = 'zenario_multiple_image_container';
	if (ze\module::isRunning($module)) {

		$imageIds = [];
		$instances = ze\module::getModuleInstancesAndPluginSettings($module);
		
		foreach ($instances as $instance) {
			if (!empty($instance['settings']['image'])) {
				foreach (explode(',', $instance['settings']['image']) as $imageId) {
					$imageIds[] = $imageId;
				}
			}
		}

		if (!empty($imageIds)) {
			//Remove duplicates
			$imageIds = array_unique($imageIds);
			$imageIds = implode(',', $imageIds);

			$imageCount = 0;
			$imagesMovedToDocstore = [];
			$imagesDuplicatedAndMovedToDocstore = [];

			//Check which of these images are stored in the DB, and move them to docstore.
			$sql = '
				SELECT id, filename
				FROM ' . DB_PREFIX . 'files
				WHERE id IN (' . ze\escape::in($imageIds, 'numeric') . ')
				AND location = "db"';
			
			$result = ze\sql::select($sql);
			while ($file = ze\sql::fetchAssoc($result)) {
				$usage = [];

				$usageSql = "
					SELECT foreign_key_to, is_nest, is_slideshow, GROUP_CONCAT(DISTINCT foreign_key_id, foreign_key_char) AS concat
					FROM ". DB_PREFIX. "inline_images
					WHERE image_id = ". (int) $file['id']. "
					AND in_use = 1
					AND archived = 0
					AND foreign_key_to IN ('content', 'library_plugin', 'menu_node', 'email_template', 'newsletter', 'newsletter_template') 
					GROUP BY foreign_key_to, is_nest, is_slideshow";
				
				$usageResults = ze\sql::fetchAssocs($usageSql);
				
				foreach ($usageResults as $usageResult) {
					$keyTo = $usageResult['foreign_key_to'];
				
					if ($keyTo == 'content') {
						$usage['content_items'] = \ze\sql::fetchValue("
							SELECT GROUP_CONCAT(foreign_key_char, '_', foreign_key_id)
							FROM ". DB_PREFIX. "inline_images
							WHERE image_id = ". (int) $file['id']. "
							AND archived = 0
							AND foreign_key_to = 'content'
						");
					
					} elseif ($keyTo == 'library_plugin') {
						if ($usageResult['is_slideshow']) {
							$usage['slideshows'] = $usageResult['concat'];
							
						} elseif ($usageResult['is_nest']) {
							$usage['nests'] = $usageResult['concat'];
						
						} else {
							$usage['plugins'] = $usageResult['concat'];
						}
						
					} else {
						$usage[$keyTo. 's'] = $usageResult['concat'];
					}
				}

				$MICPluginsAndSettings = $nonMICPluginsAndSettings = [];
				if (!empty($usage['plugins'])) {
					//Make a list of plugin types in case this image needs to be duplicated later.

					foreach (explode(',', $usage['plugins']) as $plugin) {
						$pluginSql = '
							SELECT pi.id, ps.value, m.class_name
							FROM ' . DB_PREFIX . 'plugin_instances pi
							INNER JOIN ' . DB_PREFIX . 'modules m
								ON pi.module_id = m.id
							LEFT JOIN ' . DB_PREFIX . 'plugin_settings ps
								ON ps.instance_id = pi.id
							WHERE pi.id = ' . (int)$plugin . '
							AND ps.name = "image"';
						$pluginResult = ze\sql::fetchAssoc($pluginSql);

						if ($pluginResult) {
							if ($pluginResult['class_name'] == 'zenario_multiple_image_container') {
								$MICPluginsAndSettings[$pluginResult['id']] = ['value' => $pluginResult['value']];
							} else {
								$nonMICPluginsAndSettings[$pluginResult['id']] = ['value' => $pluginResult['value']];
							}
						}
					}
				}

				if (!empty($usage['nests'])) {
					//Make a list of plugin types in case this image needs to be duplicated later.

					foreach (explode(',', $usage['nests']) as $nest) {
						$pluginSql = '
							SELECT np.instance_id AS id, np.id AS egg_id, ps.value, m.class_name
							FROM ' . DB_PREFIX . 'nested_plugins np
							INNER JOIN ' . DB_PREFIX . 'modules m
								ON np.module_id = m.id
							LEFT JOIN ' . DB_PREFIX . 'plugin_settings ps
								ON ps.instance_id = np.instance_id
								AND ps.egg_id = np.id
							WHERE np.instance_id = ' . (int)$nest . '
							AND ps.name = "image"';
						$pluginResult = ze\sql::fetchAssoc($pluginSql);

						if ($pluginResult) {
							if ($pluginResult['class_name'] == 'zenario_multiple_image_container') {
								$MICPluginsAndSettings[$pluginResult['egg_id']] = ['value' => $pluginResult['value'], 'nest_id' => $pluginResult['id']];
							} else {
								$nonMICPluginsAndSettings[$pluginResult['egg_id']] = ['value' => $pluginResult['value'], 'nest_id' => $pluginResult['id']];
							}
						}
					}
				}

				if (!empty($usage['slideshows']) || !empty($usage['content_items']) || count($nonMICPluginsAndSettings) > 0) {
					//If the image is used by anything else other than just MIC plugins, then duplicate the file with new usage value,
					//update any MIC plugin settings to use the new file ID instead,
					//and create the correct entry in the "inline_images" table.
					//Also move the file to docstore.
					$oldFileInfo = ze\row::get('files', ['filename', 'short_checksum'], ['id' => $file['id']]);
					$newFileId = ze\fileAdm::copyInDatabase('mic', $file['id'], false, true, $addToDocstoreDirIfPossible = true);

					foreach ($MICPluginsAndSettings as $pluginId => $plugin) {
						$oldImageSettings = $plugin['value'];
						
						$newImageSettingsArray = [];
						$oldImageSettingsArray = explode(',', $oldImageSettings);
						foreach ($oldImageSettingsArray as $entry) {
							if ($entry == $file['id']) {
								$newImageSettingsArray[] = $newFileId;
							} else {
								$newImageSettingsArray[] = $entry;
							}
						}
						$newImageSettings = implode(',', $newImageSettingsArray);

						$wherePluginSettings = [
							'name' => 'image',
							'foreign_key_to' => 'multiple_files',
							'value' => ze\escape::in($oldImageSettings)
						];

						$whereInlineImages = [
							'image_id' => (int)$file['id'],
							'in_use' => 1,
							'archived' => 0,
							'is_slideshow' => 0,
							'foreign_key_to' => 'library_plugin'
						];

						if (!empty($plugin['nest_id'])) {
							$wherePluginSettings['instance_id'] = (int)$plugin['nest_id'];
							$wherePluginSettings['egg_id'] = (int)$pluginId;

							$whereInlineImages['foreign_key_id'] = (int)$plugin['nest_id'];
							$whereInlineImages['is_nest'] = 1;
						} else {
							$whwherePluginSettingsere['instance_id'] = (int)$pluginId;

							$whereInlineImages['foreign_key_id'] = (int)$pluginId;
							$whereInlineImages['is_nest'] = 0;
						}

						ze\row::update('plugin_settings', ['value' => ze\escape::in($newImageSettings)], $wherePluginSettings);
						ze\row::update('inline_images', ['image_id' => (int)$newFileId], $whereInlineImages);
						$imageCount ++;
						$imagesDuplicatedAndMovedToDocstore[$file['id']] = $oldFileInfo;
						$imagesDuplicatedAndMovedToDocstore[$file['id']]['new_id'] = $newFileId;
						$imagesDuplicatedAndMovedToDocstore[$file['id']]['docstore_path'] = ze\row::get('files', 'path', ['id' => $newFileId]);
					}
				} else {
					//Alternatively, if the image is used only by MIC plugins, then just move it to docstore and update the "usage" column.
					$fileInfo = ze\row::get('files', ['filename', 'checksum', 'short_checksum'], ['id' => $file['id']]);
					
					ze\row::update('files', ['usage' => 'mic'], $file['id']);
					ze\file::moveFileFromDBToDocstore($file['id']);

					$imageCount ++;
					$imagesMovedToDocstore[$file['id']] = $fileInfo;
					$imagesMovedToDocstore[$file['id']]['docstore_path'] = $pathDS;
				}
			}

			if ($imageCount > 0) {
				$subject = ze\admin::phrase('Zenario [[zenario_version]] update: images moved to docstore', ['zenario_version' => ZENARIO_VERSION]);

				$message = ze\admin::phrase('Number of images moved to docstore: [[count]]', ['count' => (int)$imageCount]);
				$message .= "\n";

				if (count($imagesMovedToDocstore) > 0) {
					$message .= "\n";
					$message .= ze\admin::phrase('Images moved to docstore:');
					foreach ($imagesMovedToDocstore as $movedImageId => $movedImageInfo) {
						$message .= "\n";
						$message .= ze\admin::phrase(
							'ID: [[id]], filename: [[filename]], short checksum: [[short_checksum]]',
							['id' => $movedImageId, 'filename' => $movedImageInfo['filename'], 'short_checksum' => $movedImageInfo['short_checksum']]
						);
					}
				}

				if (count($imagesDuplicatedAndMovedToDocstore) > 0) {
					$message .= "\n";
					$message .= ze\admin::phrase('Images duplicated and moved to docstore:');
					foreach ($imagesDuplicatedAndMovedToDocstore as $movedImageId => $movedImageInfo) {
						$message .= "\n";
						$message .= ze\admin::phrase(
							'Previous ID: [[previous_id]], new ID: [[new_id]], filename: [[filename]], short checksum: [[short_checksum]], docstore path: [[docstore_path]]',
							[
								'previous_id' => $movedImageId,
								'new_id' => $movedImageInfo['new_id'],
								'filename' => $movedImageInfo['filename'],
								'short_checksum' => $movedImageInfo['short_checksum'],
								'docstore_path' => $movedImageInfo['docstore_path']
							]
						);
					}
				}

				ze\server::sendEmailSimple($subject, $message, $isHTML = false);
			}
		}
	}
	
	ze\dbAdm::revision(52205);
}

if (ze\dbAdm::needRevision(52219)) {
	$module = 'assetwolf_2';
	if (ze\module::isRunning($module)) {
		$instances = ze\module::getModuleInstancesAndPluginSettings($module);
		
		foreach ($instances as $instance) {
			if (!empty($instance['settings']['enable.metadata']) && $instance['settings']['enable.metadata'] == 1) {
				ze\row::update('plugin_settings', ['value' => 'show_all'], ['instance_id' => (int)$instance['instance_id'], 'egg_id' => (int)$instance['egg_id'], 'name' => 'enable.metadata']);
			}
		}
	}

	ze\dbAdm::revision(52219);
}
//For Maximum Content File Size settings we need to update value from bytes to MB
if (ze\dbAdm::needRevision(52220)) {
	$filesizevalue = ze::setting('content_max_filesize', false);
	$filesizeUnit = ze::setting('content_max_filesize_unit', false);
	
	if ($filesizevalue && !$filesizeUnit) {
		
		if ($filesizevalue < 1000000) {
			$newFileValue = 1;
			$newFileUnit = 'MB';
		} else {
			$fileSizeConvertValue = ze\file::fileSizeConvert($filesizevalue);
			$convertArray = explode(' ', $fileSizeConvertValue);
			$newFileValue = $convertArray[0];
			$newFileUnit = $convertArray[1];
		}
		
		ze\site::setSetting('content_max_filesize', $newFileValue);
		ze\site::setSetting('content_max_filesize_unit', $newFileUnit);
	
	} elseif (!$filesizevalue) {
		ze\site::setSetting('content_max_filesize', 20);
		ze\site::setSetting('content_max_filesize_unit', 'MB');
	}
	
	ze\dbAdm::revision(52220);
}



//
//	Changes for layouts in 9.0
//

if (ze\dbAdm::needRevision(52526)) {
	
	if (\ze::$dbL
	 && \ze::$dbL->con
	 && !is_dir(CMS_ROOT. 'cache/layouts')) {
		ze\cache::cleanDirs(true);
	}
	
	$missingLayoutFiles = [];
	
	//Look for all existing grid layouts, with files stored in the zenario_custom/templates/grid_templates/ directory
	foreach (ze\row::getAssocs('layouts', true) as $layout) {
		
		//Check whether a layout file exists to migrate from
		if (($is_file = is_file($path = CMS_ROOT. ($relPath = 'zenario_custom/templates/grid_templates/'. $layout['file_base_name']. '.tpl.php')))
		 && ($html = @file_get_contents($path))) {
			if (($data = ze\gridAdm::readCode($html, false, false))
			 && (!empty($data['cells']))
			 && (ze\gridAdm::validateData($data))) {
				ze\gridAdm::trimData($data);
				ze\gridAdm::saveLayoutData($layout['layout_id'], $data);
			}
		
		//If the layout doesn't already exist, check:
			//Is it active?
			//Has it already been migrated, maybe in a previous sweep?
			//Does it look like a grid layout?
		//If the answers are "yes", "no" and "yes", then raise it as a problem!
		} else {
			if ($layout['status'] == 'active'
			 && empty($layout['json_data'])
			 && !empty($layout['cols'])) {
				$missingLayoutFiles[] = $relPath;
			}
		}
	}
	
	
	if (!empty($missingLayoutFiles)) {
		$mrg = [
			'example' => $missingLayoutFiles[0]
		];
		
		echo ze\admin::nzPhrase(
			'Your layouts need migrating to Zenario 9, but [[example]] and its .css equivalent are missing or not readable.',
			'Your layouts need migrating to Zenario 9, but [[example]] and 1 other file-pair are missing from the disk or not readable.',
			'Your layouts need migrating to Zenario 9, but [[example]] and [[count]] other file-pairs are missing from the disk or not readable.',
			count($missingLayoutFiles) - 1,
			$mrg
		);
		exit;
	}
	
	if (is_dir(CMS_ROOT. 'zenario_custom/templates/grid_templates/skins/')
	 && !is_dir(CMS_ROOT. 'zenario_custom/skins/')) {
		echo ze\admin::phrase('Your skins need migrating to Zenario 9, please move the zenario_custom/templates/grid_templates/skins/ directory to zenario_custom/skins/');
		exit;
	}
	
	ze\dbAdm::revision(52526);
}


//Drop the file_base_name column, now we don't need it any more
if (ze\dbAdm::needRevision(52527) && !ze\sql::numRows('SHOW COLUMNS FROM '. DB_PREFIX. 'layouts LIKE "file_base_name"')) ze\dbAdm::revision(52527
, <<<_sql
	ALTER TABLE `[[DB_PREFIX]]layouts` 
	DROP COLUMN `file_base_name`
_sql
);


//In 9.0, some of the debug options for the page/plugin caching have changed.
//Clear the old values, so I don't have to deal with any unsupported changes.
if (ze\dbAdm::needRevision(53250)) {
	ze\site::setSetting('caching_debug_info', '');
	ze\site::setSetting('limit_caching_debug_info_by_ip', '');
	ze\dbAdm::revision(53250);
}

//Clear the cache after processing the skins
if (ze\dbAdm::needRevision(53251)) {
	ze\skinAdm::clearCache();
}

//Event calendar module settings update
if (ze\dbAdm::needRevision(53601)) {
	if (ze\module::isRunning('zenario_event_calendar')) {
		$instances = ze\module::getModuleInstancesAndPluginSettings('zenario_event_calendar');
		
		foreach ($instances as $instance) {
			if ($instance['settings']['event_count'] == 'event_count_on') {
				$newValue = 1;
			} else {
				$newValue = 0;
			}
			ze\row::update('plugin_settings', ['value' => (int) $newValue], ['instance_id' => (int) $instance['instance_id'], 'egg_id' => (int) $instance['egg_id'], 'name' => 'event_count']);
			
			ze\row::insert('plugin_settings',
				[
					'instance_id' => (int) $instance['instance_id'],
					'egg_id' => (int) $instance['egg_id'],
					'name' => 'enable_popup',
					'value' => 1
				],
				$ignore = true
			);

			if ($instance['settings']['view_mode'] == 'year_view') {
				ze\row::insert('plugin_settings',
					[
						'instance_id' => (int) $instance['instance_id'],
						'egg_id' => (int) $instance['egg_id'],
						'name' => 'show_other_periods',
						'value' => 'previous_and_next'
					],
					$ignore = true
				);
			}

			if (!empty($instance['settings']['first_event'])) {
				$value = 'first_event';
			} else {
				$value = 'all_events';
			}

			ze\row::insert('plugin_settings',
				[
					'instance_id' => (int) $instance['instance_id'],
					'egg_id' => (int) $instance['egg_id'],
					'name' => 'show_event_titles',
					'value' => ze\escape::sql($value)
				],
				$ignore = true
			);
		}
	}

	ze\dbAdm::revision(53601);
}

//Event Calendar settings update: in the popup, the title is now mandatory,
//and the setting can be removed to avoid clutter.
if (ze\dbAdm::needRevision(53602)) {
	if (ze\module::isRunning('zenario_event_calendar')) {
		$instances = ze\module::getModuleInstancesAndPluginSettings('zenario_event_calendar');
		
		foreach ($instances as $instance) {
			ze\row::delete('plugin_settings',
				[
					'instance_id' => (int) $instance['instance_id'],
					'egg_id' => (int) $instance['egg_id'],
					'name' => 'show_title'
				]
			);
		}
	}

	ze\dbAdm::revision(53602);
}

//In 9.1, there were 2 removed frameworks for Menu Vertical plugins.
//Their features were integrated with the standard framework as plugin settings.
//This logic will set the framework to standard, and enable the relevant setting.
if (ze\dbAdm::needRevision(53802)) {
	$module = 'zenario_menu_vertical';
	if (ze\module::isRunning($module)) {
		$moduleId = ze\row::get('modules', 'id', ['class_name' => $module]);

		//Non-nested plugins
		$pluginInstancesSql = '
			SELECT id, framework
			FROM ' . DB_PREFIX . 'plugin_instances
			WHERE module_id = ' . (int)$moduleId . '
			AND framework IN ("menu_with_name", "menu_with_title")';
		$pluginInstancesResult = \ze\sql::select($pluginInstancesSql);

		while ($row = \ze\sql::fetchAssoc($pluginInstancesResult)) {
			if ($row['framework'] == 'menu_with_title') {
				ze\row::set('plugin_settings', ['value' => 1], ['instance_id' => $row['id'], 'egg_id' => 0, 'name' => 'show_parent_menu_node_text']);
			} elseif ($row['framework'] == 'menu_with_name') {
				ze\row::set('plugin_settings', ['value' => 1], ['instance_id' => $row['id'], 'egg_id' => 0, 'name' => 'change_welcome_message']);
			}
			ze\row::set('plugin_instances', ['framework' => 'standard'], ['id' => $row['id']]);
		}
		
		//Nested plugins
		$nestedPluginInstancesSql = '
			SELECT instance_id, id, framework
			FROM ' . DB_PREFIX . 'nested_plugins
			WHERE module_id = ' . (int)$moduleId . '
			AND framework IN ("menu_with_name", "menu_with_title")';
		$nestedPluginsResult = \ze\sql::select($nestedPluginInstancesSql);

		while ($row = \ze\sql::fetchAssoc($nestedPluginsResult)) {
			if ($row['framework'] == 'menu_with_title') {
				ze\row::set('plugin_settings', ['value' => 1], ['instance_id' => $row['instance_id'], 'egg_id' => $row['id'], 'name' => 'show_parent_menu_node_text']);
			} elseif ($row['framework'] == 'menu_with_name') {
				ze\row::set('plugin_settings', ['value' => 1], ['instance_id' => $row['instance_id'], 'egg_id' => $row['id'], 'name' => 'change_welcome_message']);
			}
			
			ze\row::set('nested_plugins', ['framework' => 'standard'], ['instance_id' => $row['instance_id'], 'id' => $row['id']]);
		}
	}

	ze\dbAdm::revision(53802);
}

//WebP support: remove the public images folder and recreate it, so that webp images are generated.
if (ze\dbAdm::needRevision(53803)) {
	//Change the time limit to 10 min
	//in case there are multiple images being created
	//on the Diagnostics screen.
	set_time_limit(60 * 10);

	$publicImagesDir = CMS_ROOT . 'public/images';
	if (is_dir($publicImagesDir)) {
		if (!\ze\server::isWindows() && \ze\server::execEnabled()) {
			exec('rm -rf '. escapeshellarg($publicImagesDir));
		} else {
			ze\cache::deleteDir($publicImagesDir, 2);
		}
		
		ze\cache::cleanDirs($force = true);
	}

	ze\dbAdm::revision(53803);
}

//Writer profiles: create writer profiles for admins
//and update the content item values to use the profile instead of admin ID.
if (ze\dbAdm::needRevision(54404)) {
	//Get a list of admins used as content item writers.
	$sql = "
		SELECT DISTINCT a.id
		FROM " . DB_PREFIX . "content_item_versions v
		INNER JOIN " . DB_PREFIX . "content_items ci
			ON ci.id = v.id
			AND ci.type = v.type
		INNER JOIN " . DB_PREFIX . "admins a
			ON a.id = v.writer_id
		WHERE ci.status IN('published', 'published_with_draft')";
	$result = ze\sql::select($sql);
	$adminWriters = ze\sql::fetchValues($result);

	if (is_array($adminWriters) && count($adminWriters) > 0) {
		//Create writer profiles for these admins.
		$adminWriterProfiles = [];

		foreach ($adminWriters as $index => $adminId) {
			$adminDetails = ze\admin::details($adminId);

			$createdInfo = [];
			ze\admin::setLastUpdated($createdInfo, true);

			$writerProfileId = ze\row::set(
				'writer_profiles',
				[
					'admin_id' => (int) $adminId,
					'first_name' => ze\escape::sql(($adminDetails['first_name'] ?? '')),
					'last_name' => ze\escape::sql(($adminDetails['last_name'] ?? '')),
					'email' => ze\escape::sql(($adminDetails['email'] ?? '')),
					'type' => 'administrator',
					'created' => $createdInfo['created'],
					'created_admin_id' => $createdInfo['created_admin_id']
				],
				[]
			);

			$adminWriterProfiles[$adminId] = $writerProfileId;
		}

		//Update the content item metadata to use the new writer profile instead of admin ID.
		if (count($adminWriterProfiles) > 0) {
			$sql = "
				SELECT v.id, v.type, v.version, v.writer_id
				FROM " . DB_PREFIX . "content_item_versions v
				INNER JOIN " . DB_PREFIX . "content_items ci
					ON ci.id = v.id
					AND ci.type = v.type
				WHERE v.writer_id IN (" . ze\escape::in($adminWriters) . ")
				AND ci.status IN('published', 'published_with_draft')";
			$result = ze\sql::select($sql);

			while ($row = ze\sql::fetchAssoc($result)) {
				ze\row::set(
					'content_item_versions',
					[
						'writer_id' => (int) $adminWriterProfiles[$row['writer_id']]
					],
					[
						'id' => $row['id'],
						'type' => $row['type'],
						'version' => $row['version'],
						'writer_id' => $row['writer_id']
					]
				);
			}
		}
	}

	//Afterwards, remove dangling cross-references.
	$sql = "
		UPDATE " . DB_PREFIX . "content_item_versions v
		INNER JOIN " . DB_PREFIX . "content_items ci
			ON ci.id = v.id
			AND ci.type = v.type
		SET v.writer_id = 0
		WHERE ci.status NOT IN('published', 'published_with_draft')";
	$result = ze\sql::update($sql);

	ze\dbAdm::revision(54404);
}

//In 9.2, there was a new plugin setting and 1 removed framework for Multiple Image Container plugin.
//This logic will set the framework to standard, and enable the relevant setting.
if (ze\dbAdm::needRevision(54631)) {
	$module = 'zenario_multiple_image_container';
	if (ze\module::isRunning($module)) {
		$moduleId = ze\row::get('modules', 'id', ['class_name' => $module]);

		//Non-nested plugins
		$pluginInstancesSql = '
			SELECT id, framework
			FROM ' . DB_PREFIX . 'plugin_instances
			WHERE module_id = ' . (int)$moduleId . '
			AND framework = "image_then_title"';
		$pluginInstancesResult = \ze\sql::select($pluginInstancesSql);

		while ($row = \ze\sql::fetchAssoc($pluginInstancesResult)) {
			ze\row::set('plugin_settings', ['value' => 1], ['instance_id' => $row['id'], 'egg_id' => 0, 'name' => 'show_caption_above_thumbnail']);
			ze\row::set('plugin_instances', ['framework' => 'standard'], ['id' => $row['id']]);
		}
		
		//Nested plugins
		$nestedPluginInstancesSql = '
			SELECT instance_id, id, framework
			FROM ' . DB_PREFIX . 'nested_plugins
			WHERE module_id = ' . (int)$moduleId . '
			AND framework = "image_then_title"';
		$nestedPluginsResult = \ze\sql::select($nestedPluginInstancesSql);

		while ($row = \ze\sql::fetchAssoc($nestedPluginsResult)) {
			ze\row::set('plugin_settings', ['value' => 1], ['instance_id' => $row['instance_id'], 'egg_id' => $row['id'], 'name' => 'show_caption_above_thumbnail']);
			ze\row::set('nested_plugins', ['framework' => 'standard'], ['instance_id' => $row['instance_id'], 'id' => $row['id']]);
		}
	}

	ze\dbAdm::revision(54631);
}

//Fix blank phrases in Extranet Base Login module
if (ze\dbAdm::needRevision(54801)) {
	$module = 'zenario_extranet';
	if (ze\module::isRunning($module)) {
		$defaultLangId = ze::$defaultLang;

		$instances = ze\module::getModuleInstancesAndPluginSettings($module);

		if (!empty($instances) && is_array($instances)) {
			foreach ($instances as $instance) {
				foreach ([
					'invalid_email_error_text' => "Your email address didn't appear to be in a valid format.",		
					'screen_name_required_error_text' => "Please enter your screen name.",		
					'email_address_required_error_text' => "Please enter your email address.",			
					'password_required_error_text' => "Please enter your password.",		
					'no_new_password_error_text' => "Please enter new password.",
					'no_new_repeat_password_error_text' => "Please repeat your new password."
				] as $fieldName => $value) {
					if (isset($instance['settings'][$fieldName]) && !$instance['settings'][$fieldName]) {
						ze\row::update(
							'plugin_settings',
							['value' => ze\escape::sql($value)],
							['name' => ze\escape::sql($fieldName), 'instance_id' => $instance['instance_id'], 'egg_id' => $instance['egg_id']]
						);
					}
				}
			}
		}
	}

	ze\dbAdm::revision(54801);
}

//In 9.3, the "floor" schematic object type was renamed to "room".
//Rename the site settings to match...
if (ze\dbAdm::needRevision(55152)) {
	$module = 'assetwolf_2_schematics_2';
	if (ze\module::isRunning($module)) {
		foreach (['assetwolf_schematic_floor_room', 'assetwolf_schematic_floor_female', 'assetwolf_schematic_floor_male', 'assetwolf_schematic_floor_disabled', 'assetwolf_schematic_floor_family'] as $oldSettingName) {
			$value = ze::setting($oldSettingName);

			switch ($oldSettingName) {
				case 'assetwolf_schematic_floor_room':
					$newSettingName = 'assetwolf_schematic_room_generic';
					break;
				case 'assetwolf_schematic_floor_female':
					$newSettingName = 'assetwolf_schematic_room_female';
					break;
				case 'assetwolf_schematic_floor_male':
					$newSettingName = 'assetwolf_schematic_room_male';
					break;
				case 'assetwolf_schematic_floor_disabled':
					$newSettingName = 'assetwolf_schematic_room_disabled';
					break;
				case 'assetwolf_schematic_floor_family':
					$newSettingName = 'assetwolf_schematic_room_family';
					break;
			}

			ze\site::setSetting($newSettingName, $value);
			ze\row::delete('site_settings', ['name' => $oldSettingName]);
		}
	}

	ze\dbAdm::revision(55152);
}

//... and the plugin settings too.
if (ze\dbAdm::needRevision(55153)) {
	$module = 'assetwolf_2_schematics_2';
	if (ze\module::isRunning($module)) {
		$instances = ze\module::getModuleInstancesAndPluginSettings($module);
		
		foreach ($instances as $instance) {
			if (!empty($instance['settings']['enable.schematic_floors']) && $instance['settings']['enable.schematic_floors'] == 1) {
				ze\row::set('plugin_settings', ['value' => 1], ['instance_id' => (int)$instance['instance_id'], 'egg_id' => (int)$instance['egg_id'], 'name' => 'enable.add_a_room']);
			}
		}

		ze\row::delete('plugin_settings', ['name' => 'enable.schematic_floors']);
	}

	ze\dbAdm::revision(55153);
}

//In 9.3, the "Cookie consent" plugin setting in HTML snippet and Twig snippet
//had one value removed. Migrate value 3 to value 2.
if (ze\dbAdm::needRevision(55601)) {
	$modules = ['zenario_html_snippet', 'zenario_twig_snippet'];
	foreach ($modules as $module) {
		if (ze\module::isRunning($module)) {
			$instances = ze\module::getModuleInstancesAndPluginSettings($module);
			
			foreach ($instances as $instance) {
				if (!empty($instance['settings']['cookie_consent']) && $instance['settings']['cookie_consent'] == 'required') {
					ze\row::update('plugin_settings', ['value' => 'needed'], ['instance_id' => (int)$instance['instance_id'], 'egg_id' => (int)$instance['egg_id'], 'name' => 'cookie_consent']);
				}
			}
		}
	}

	ze\dbAdm::revision(55601);
}

if (ze\dbAdm::needRevision(56251)) {
	$module = 'zenario_location_map_and_listing_2';
	if (ze\module::isRunning($module)) {
		$instances = ze\module::getModuleInstancesAndPluginSettings($module);
		
		foreach ($instances as $instance) {
			if (!empty($instance['settings']['location_display']) && $instance['settings']['hide_filters_list'] == 'show_all_locations') {
				if (!empty($instance['settings']['hide_filters_list']) && $instance['settings']['hide_filters_list'] == 1) {
					ze\row::set('plugin_settings', ['value' => 'show_all_locations_with_filter'], ['instance_id' => (int)$instance['instance_id'], 'egg_id' => (int)$instance['egg_id'], 'name' => 'location_display']);
				}
			}
		}

		ze\row::delete('plugin_settings', ['name' => 'hide_filters_list']);
	}

	ze\dbAdm::revision(56251);
}


//Clear out the cache/tuix/ directory, as I've changed the naming format
//Note: This is a re-issued revision. It's been run before in previous versions, but I'm bumping it
//as I want to run it again.
if (ze\dbAdm::needRevision(56600)) {
	
	if (is_dir(CMS_ROOT. 'cache/tuix')) {
		ze\row::update('tuix_file_contents', ['last_modified' => 0, 'checksum' => ''], []);
		ze\cache::deleteDir(CMS_ROOT. 'cache/tuix', 1);
		ze\cache::cleanDirs(true);
	}
	
	ze\dbAdm::revision(56600);
}


//A little bit more migration for the data in the layout_head_and_foot table.
//In the step 2 migraiton file, we tried to match a layout to the header/footer that looked like
//it was the most common one, and copy the basic settings.
//There are some more settings that need to be copied in the JSON data though, so try to come back
//and copy those now.
//Note: this will only work when migrating an existing site, not when doing a fresh install
//or site reset. There's another revision just below that will handle this case.
if (ze\dbAdm::needRevision(56660)) {
	
	$headerInfo = ze\row::get('layout_head_and_foot', true, ['for' => 'sitewide']);
	if ($headerInfo) {
		if ($layoutData = ze\row::get('layouts', 'json_data', [
			'cols' => $headerInfo['cols'],
			'min_width' => $headerInfo['min_width'],
			'max_width' => $headerInfo['max_width'],
			'fluid' => $headerInfo['fluid'],
			'responsive' => $headerInfo['responsive']
		])) {
			if (is_array($layoutData) && !empty($layoutData)) {
				unset($layoutData['cells']);
				ze\row::update('layout_head_and_foot', [
					'head_json_data' => $layoutData,
					'foot_json_data' => (object) []		//N.b. I want an empty object here, not an empty array.
				], []);
			}
		}
	}
	
	ze\dbAdm::revision(56660);
}


//Catch the case where this is a fresh install, site reset, or we couldn't migrate the settings
//from a layout to the site-wide header.
if (ze\dbAdm::needRevision(56790)) {
	
	$headerInfo = ze\row::get('layout_head_and_foot', true, ['for' => 'sitewide']);
	if (!$headerInfo
	  || empty($headerInfo['cols'])
	  || empty($headerInfo['head_json_data'])) {
		
		//Several things in the CMS will break if there is no layout header information.
		//Save some default values into the header to address this. 
		$data = \ze\gridAdm::sensibleDefault();
		$details = ['foot_json_data' => (object) []];	//N.b. I want an empty object here, not an empty array.
		
		ze\gridAdm::trimData($data);
		ze\gridAdm::updateHeaderMetaInfoInDB($data, $details);
	}
	
	ze\dbAdm::revision(56790);
}


//We've recently discovered a bug where some SVG files had their width and height incorrectly
//read as 100✖️100 because our functions incorrectly read their metadata.
//This has been fixed, but we need to watch out for any SVG that looks like it might have been
//affected and rescan it.
if (ze\dbAdm::needRevision(57200)) {
	
	if (function_exists('simplexml_load_string')) {
		foreach (ze\row::getValues('files', 'id', [
			'mime_type' => 'image/svg+xml', 'width' => 100, 'height' => 100
		]) as $imageId) {
			$file = ze\row::get('files', ['width', 'height', 'data'], $imageId);
			
			if (ze\fileAdm::getWidthAndHeightOfSVG($file, $file['data'])) {
				unset($file['data']);
				ze\row::update('files', $file, $imageId);
			}
			unset($file);
		}
	}

	ze\dbAdm::revision(57200);
}

//In 9.4, the Maximum user image file size setting was updated
//to match similar changes to maximum file upload size from revision 52220
//(from a field that only accepts bytes to a shorter field with a unit selector).
//Update the value to work correctly.
if (ze\dbAdm::needRevision(57211)) {
	if (ze\module::inc('zenario_users')) {
		$filesizevalue = ze::setting('max_user_image_filesize', false);
		$filesizeUnit = ze::setting('max_user_image_filesize_unit', false);
	
		if ($filesizevalue && !$filesizeUnit) {
		
			if ($filesizevalue < 1000000) {
				$newFileValue = 50;
				$newFileUnit = 'KB';
			} else {
				$fileSizeConvertValue = ze\file::fileSizeConvert($filesizevalue);
				$convertArray = explode(' ', $fileSizeConvertValue);
				$newFileValue = $convertArray[0];
				$newFileUnit = $convertArray[1];
			}
		
			ze\site::setSetting('max_user_image_filesize', $newFileValue);
			ze\site::setSetting('max_user_image_filesize_unit', $newFileUnit);
	
		} elseif (!$filesizevalue) {
			ze\site::setSetting('max_user_image_filesize', 50);
			ze\site::setSetting('max_user_image_filesize_unit', 'KB');
		}
	}
	
	ze\dbAdm::revision(57211);
}

//On a very small number of sites, people replied on being able to access the ze\row library in Twig Snippets,
//however this has recently been locked down as a measure to improve security.
//They now need to specifically create functions for the queries they'll need, and whitelist them.
//There are actually only a few small number of places where we know someone has done this, so I can write
//a SQL statement that should catch and fix the known bad cases.
if (ze\dbAdm::needRevision(57700)) {
	
	$sql = "
		SELECT id, custom_yaml
		FROM ". DB_PREFIX. "tuix_snippets
		WHERE custom_yaml LIKE '%ze(%'
	";
	
	foreach (ze\sql::select($sql) as $ts) {
		
		$ts['custom_yaml'] = str_replace(
			'ze(\'date\', \'relative\'',
			'ze(\'date\', \'formatRelativeDateTime\'',
		$ts['custom_yaml']);
		
		$ts['custom_yaml'] = str_replace(
			'ze(\'user\', \'can\'',
			'ze(\'user\', \'currentUserCan\'',
		$ts['custom_yaml']);
		
		$ts['custom_yaml'] = str_replace(
			'ze(\'row\', \'get\', constant(\'ASSETWOLF_2_PREFIX\') ~ \'nodes\', \'schema_id\', nodeId)',
			'ze(\'assetwolf\', \'nodeSchemaId\', nodeId)',
		$ts['custom_yaml']);
		
		$ts['custom_yaml'] = str_replace(
			'ze(\'row\', \'get\', constant(\'ASSETWOLF_2_PREFIX\') ~ \'schema_fields\', \'metric_id\', {"key" : id, "schema_id": schemaId})',
			'ze(\'assetwolf\', \'fieldMetricId\', id, schemaId)',
		$ts['custom_yaml']);
		
		$ts['custom_yaml'] = str_replace(
			'ze(\'row\', \'get\', constant(\'ASSETWOLF_2_PREFIX\') ~ \'metrics\', \'period\', metricId)',
			'ze(\'assetwolf\', \'metricPeriod\', metricId)',
		$ts['custom_yaml']);
		
		$ts['custom_yaml'] = str_replace(
			'ze(\'row\', \'get\', constant(\'ASSETWOLF_2_PREFIX\') ~ \'metrics\', \'run_frequency\', metricId)',
			'ze(\'assetwolf\', \'metricRunFrequency\', metricId)',
		$ts['custom_yaml']);
		
		$ts['custom_yaml'] = str_replace(
			'ze(\'row\\\\da\', \'getValues\', \'datapoints\', \'timestamp_1\', {node_id: item.id}, [\'timestamp_1\', \'DESC\'], false, false, 2)',
			'ze(\'assetwolf\', \'lastTwoDatapointsFromNode\', item.id)',
		$ts['custom_yaml']);
		
		
		try {
			$tuix = \Spyc::YAMLLoadString(trim($ts['custom_yaml']));
			$ts['custom_json'] = json_encode($tuix, JSON_FORCE_OBJECT);
	
		} catch (\Exception $e) {
			$ts['custom_json'] = '';
		}
		
		ze\row::update('tuix_snippets', [
			'custom_yaml' => $ts['custom_yaml'],
			'custom_json' => $ts['custom_json']
		], $ts['id']);
	}
	
	ze\dbAdm::revision(57700);
}


//Same as above, but this time replace a line where the constant() function was used
if (ze\dbAdm::needRevision(57710)) {
	
	$sql = "
		SELECT id, custom_yaml
		FROM ". DB_PREFIX. "tuix_snippets
		WHERE custom_yaml LIKE '%constant(%'
	";
	
	foreach (ze\sql::select($sql) as $ts) {
		
		$ts['custom_yaml'] = str_replace(
			'constant(\'ASSETWOLF_TSM\')',
			'ze(\'assetwolf\', \'timestampMultipler\')',
		$ts['custom_yaml']);
		
		
		try {
			$tuix = \Spyc::YAMLLoadString(trim($ts['custom_yaml']));
			$ts['custom_json'] = json_encode($tuix, JSON_FORCE_OBJECT);
	
		} catch (\Exception $e) {
			$ts['custom_json'] = '';
		}
		
		ze\row::update('tuix_snippets', [
			'custom_yaml' => $ts['custom_yaml'],
			'custom_json' => $ts['custom_json']
		], $ts['id']);
	}
	
	ze\dbAdm::revision(57710);
}

//In 9.5, the Maximum location image file size setting was updated
//from a field that only accepts bytes to a shorter field with a unit selector.
//Update the value to work correctly.
if (ze\dbAdm::needRevision(57776)) {
	if (ze\module::inc('zenario_location_manager')) {
		$filesizevalue = ze::setting('max_location_image_filesize', false);
		$filesizeUnit = ze::setting('max_location_image_filesize_unit', false);
	
		if ($filesizevalue && !$filesizeUnit) {
			//The default value was 50 KB, so keep that, but if the old value would have reached 1 GB or more, cap it at 1023 MB.
			if ($filesizevalue < 1000000) {
				$newFileValue = 50;
				$newFileUnit = 'KB';
			} elseif ($filesizevalue >= 1072693248) {
				$newFileValue = 1023;
				$newFileUnit = 'MB';
			} else {
				$fileSizeConvertValue = ze\file::fileSizeConvert($filesizevalue);
				$convertArray = explode(' ', $fileSizeConvertValue);
				$newFileValue = $convertArray[0];
				$newFileUnit = $convertArray[1];
			}
		
			ze\site::setSetting('max_location_image_filesize', $newFileValue);
			ze\site::setSetting('max_location_image_filesize_unit', $newFileUnit);
	
		} elseif (!$filesizevalue) {
			ze\site::setSetting('max_location_image_filesize', 50);
			ze\site::setSetting('max_location_image_filesize_unit', 'KB');
		}
	}
	
	ze\site::setSetting('max_location_image_filesize_override', 'limit_max_image_file_size');
	
	ze\dbAdm::revision(57776);
}

//In 9.5, the maximum image/attachment file size settings were further redesigned.
//Now, the "Override" setting is a checkbox, not a radio anymore.
//Tidy up the settings.
if (ze\dbAdm::needRevision(57777)) {
	if (ze\module::inc('zenario_users')) {
		$userImageOverrideValue = ze::setting('max_user_image_filesize_override', false);
	
		if ($userImageOverrideValue == 'use_global_max_attachment_file_size') {
			ze\site::setSetting('max_user_image_filesize_override', false);
		} else {
			ze\site::setSetting('max_user_image_filesize_override', true);
		}
	}
	
	if (ze\module::inc('zenario_location_manager')) {
		$locationImageOverrideValue = ze::setting('max_location_image_filesize_override', false);
	
		if ($locationImageOverrideValue == 'use_global_max_image_file_size') {
			ze\site::setSetting('max_location_image_filesize_override', false);
		} else {
			ze\site::setSetting('max_location_image_filesize_override', true);
		}
	}
	
	if (ze\module::inc('zenario_user_forms')) {
		$formAttachmentOverrideValue = ze::setting('zenario_user_forms_max_attachment_file_size_override', false);
	
		if ($formAttachmentOverrideValue == 'use_global_max_attachment_file_size') {
			ze\site::setSetting('zenario_user_forms_max_attachment_file_size_override', false);
		} else {
			ze\site::setSetting('zenario_user_forms_max_attachment_file_size_override', true);
		}
	}
	
	ze\dbAdm::revision(57777);
}

//Update the Captcha radios to correctly migrate if a site was using Captcha.
//Please note: this was backpatched from 9.5 to 9.4, and is safe to run multiple times.
if (ze\dbAdm::needRevision(57983)) {
	$captchaRadiosValue = ze::setting('captcha_status_and_version');
	if ($captchaRadiosValue == 'not_enabled') {
		$siteKey = ze::setting('google_recaptcha_site_key');
		$secretKey = ze::setting('google_recaptcha_secret_key');
		
		if (!empty($siteKey) && !empty($secretKey)) {
			ze\site::setSetting('captcha_status_and_version', 'enabled_v2');
		}
	}
	
	ze\dbAdm::revision(57983);
}

//Rename a couple of site settings in 9.6
if (ze\dbAdm::needRevision(58710)) {
	foreach ([
		'css_wrappers' => 'bundle_skins',
		'cache_css_js_wrappers' => 'cache_bundles'
	] as $oldSettingName => $newSettingName) {
		
		$value = ze::setting($oldSettingName);
		
		if (!empty($value)) {
			ze\site::setSetting($newSettingName, $value);
		}
		
		ze\row::delete('site_settings', ['name' => $oldSettingName]);
	}

	ze\dbAdm::revision(58710);
}

//Update the plugin settings Twig Snippet with reset to the changes to the caching system
if (ze\dbAdm::needRevision(58730)) {
	
	foreach (ze\module::getModuleInstancesAndPluginSettings('zenario_twig_snippet') as $instance) {
		
		if (!empty($instance['settings']['if_get_set'])
		 && !empty($instance['settings']['if_post_set'])) {
			ze\pluginAdm::setSetting('if_get_or_post_var_set', 1, $instance['instance_id'], $instance['egg_id']);
		}
		if (!empty($instance['settings']['if_session_set'])
		 && !empty($instance['settings']['if_cookie_set'])) {
			ze\pluginAdm::setSetting('if_session_var_or_cookie_set', 1, $instance['instance_id'], $instance['egg_id']);
		}
		
		ze\pluginAdm::deleteSettingFromInstance($instance, 'if_get_set');
		ze\pluginAdm::deleteSettingFromInstance($instance, 'if_post_set');
		ze\pluginAdm::deleteSettingFromInstance($instance, 'if_session_set');
		ze\pluginAdm::deleteSettingFromInstance($instance, 'if_cookie_set');
		ze\pluginAdm::deleteSettingFromInstance($instance, 'clear_by_file');
	}

	ze\dbAdm::revision(58730);
}

//In 9.6, we're changing the required/read only checkboxes of the dataset editor
//to be in line with User Forms: there will now be a selector with the values
//mandatory/read only/mandatory on condition/mandatory if visible.
//This was already done in step 2, and step 4 addresses cases where a field was mandatory and read only
//at the same time. They will now be marked as read only.
ze\dbAdm::revision(58750
, <<<_sql
	UPDATE `[[DB_PREFIX]]custom_dataset_fields`
	SET
		`required` = 0,
		`required_message` = NULL
	WHERE `required` = 1 AND `readonly` = 1
_sql
);

//We no longer want to encrypt the SMTP password in 9.6.
//We've changed the flag on it so it would be automatically decrypted the next time it was
//saved, but we don't want to wait for someone to manually open and save the FAB, so we'll
//specifically do it now.
if (ze\dbAdm::needRevision(58900)) {
	
	if ($value = ze::secretSetting('smtp_password')) {
		ze\site::setSecretSetting('smtp_password', $value);
	}

	ze\dbAdm::revision(58900);
}


//In 9.6 we've split the cache/pages/ directory out into three different directories. The
//existing directory for the page cache system, one for plugins, and the bundles directory for cached bundles.
if (ze\dbAdm::needRevision(58920)) {
	
	//Clear out any files in the existing directories.
	//Try to use rm to clear the directory first if possible, as that will be much quicker.
	if (is_dir(CMS_ROOT. 'cache/pages')
	 && !ze\server::isWindows()
	 && ze\server::execEnabled()) {
		exec('rm -r '. escapeshellarg(CMS_ROOT. 'cache/pages'));
	}
	ze\skinAdm::clearCacheDir();
	
	//Call the cleanDirs() function to make sure the new directories are created.
	ze\cache::cleanDirs(true);
	
	
	ze\dbAdm::revision(58920);
}

//In 9.6, we're removing an obsolete setting.
if (ze\dbAdm::needRevision(58921)) {
	
	ze\row::delete('site_settings', ['name' => 'email_address_admin']);
	
	ze\dbAdm::revision(58921);
}

//In 9.6, we introduced a new panel: special images library.
//All image-related settings in the "Logos and branding" admin box will now be displayed in the new panel.
//Migrate previously selected settings.
//Please note: the "Logos and branding" admin box already had logic to automatically save uploaded images to the correct pool,
//but this logic should clean up images which were selected for a long time if needed.
if (ze\dbAdm::needRevision(58922)) {
	
	$settingsToMigrate = ['favicon', 'mobile_icon', 'default_icon', 'zenario_logo', 'admin_link_custom_logo', 'custom_organizer_favicon'];
	
	foreach ($settingsToMigrate as $settingToMigrate) {
		if (ze::in($settingToMigrate, 'zenario_logo', 'admin_link_custom_logo', 'custom_organizer_favicon')) {
			if (
				($settingToMigrate == 'custom_logo' && (ze::setting('brand_logo') != 'custom'))
				|| ($settingToMigrate == 'admin_link_custom_logo' && (ze::setting('admin_link_logo') != 'custom'))
				|| ($settingToMigrate == 'custom_organizer_favicon' && (ze::setting('organizer_favicon') != 'custom'))
			) {
				continue;
			}
		}
		
		$selectedImage = ze::setting($settingToMigrate);
		
		if ($selectedImage) {
			$file = ze\row::get('files', ['filename', 'usage'], $selectedImage);
		
			if (!empty($file) && $file['usage'] != 'site_setting') {
				$newFileId = ze\fileAdm::copyInDatabase('site_setting', $selectedImage, $file['filename'], $mustBeAnImage = true, $addToDocstoreDirIfPossible = false);
				
				ze\site::setSetting($settingToMigrate, $newFileId);
			}
		}
	}
	
	ze\dbAdm::revision(58922);
}

//In 9.6, we removed 2 site settings for categories.
if (ze\dbAdm::needRevision(59050)) {
	ze\row::delete('site_settings', ['name' => 'enable_display_categories_on_content_lists']);
	ze\row::delete('site_settings', ['name' => 'enable_category_landing_pages']);
	
	ze\dbAdm::revision(59050);
}

//Replace the cookie phrase codes with site settings where necessary.
//In cases where these were hardcoded messages, remove the visitor phrases.
if (ze\dbAdm::needRevision(59060)) {
	$defaultLang = ze::$defaultLang;
	
	//These phrases were editable. Migrate if needed.
	$phrasesToOverride = [
		'_COOKIE_BOX1_01_IMPLIED_MSG' => 'cookie_box1_01_implied_msg',
		'_COOKIE_BOX1_02_CONTINUE_BTN' => 'cookie_box1_02_continue_btn',
		'_COOKIE_BOX1_03_COOKIE_CONSENT_MSG' => 'cookie_box1_03_cookie_consent_msg',
		'_COOKIE_BOX1_04_MANAGE_BTN' => 'cookie_box1_04_manage_btn',
		'_COOKIE_BOX1_05_ACCEPT_BTN' => 'cookie_box1_05_accept_btn',
		'_COOKIE_BOX2_01_INTRO_MSG' => 'cookie_box2_01_intro_msg',
		'_COOKIE_BOX2_02_ACCEPT_ALL_BTN' => 'cookie_box2_02_accept_all_btn',
		'_COOKIE_BOX2_11_SAVE_PREFERENCES_BTN' => 'cookie_box2_11_save_preferences_btn'
	];
	
	foreach ($phrasesToOverride as $phraseCode => $siteSettingName) {
		if ($value = ze\row::get('visitor_phrases', 'local_text', ['module_class_name' => 'zenario_common_features', 'language_id' => $defaultLang, 'code' => $phraseCode])) {
			ze\site::setSetting($siteSettingName, $value);
		}
	}
	
	//These phrases were hardcoded. Remove them...
	$phrasesToDelete = [
		'_COOKIE_BOX2_03_NECESSARY_HEADER_1',
		'_COOKIE_BOX2_04_NECESSARY_MSG_1',
		'_COOKIE_BOX2_05_FUNCT_HEADER_2',
		'_COOKIE_BOX2_06_FUNCT_MSG_2',
		'_COOKIE_BOX2_07_ANALYTICS_HEADER_3',
		'_COOKIE_BOX2_08_ANALYTICS_MSG_3',
		'_COOKIE_BOX2_09_SOC_MEDIA_HEADER_4',
		'_COOKIE_BOX2_10_SOC_MEDIA_MSG_4'
	];
	
	//... and also remove the editable phrases from before.
	$allPhrasesToDelete = array_merge($phrasesToDelete, array_keys($phrasesToOverride));
	
	$sql = "
		DELETE FROM " . DB_PREFIX . "visitor_phrases
		WHERE module_class_name = 'zenario_common_features'
		AND code IN (" . ze\escape::in($allPhrasesToDelete) . ")";
	ze\sql::update($sql);
	
	ze\dbAdm::revision(59060);
}

//Update the metadata stored in the layout_slot_link table, as we have added another column to track more info
if (ze\dbAdm::needRevision(59330)) {
	
	foreach (ze\row::getAssocs('layouts', ['layout_id', 'json_data']) as $layout) {
		if (!empty($layout['json_data'])) {
			ze\gridAdm::updateMetaInfoInDB($layout['json_data'], $layout['layout_id']);
		}
	}
	
	ze\dbAdm::revision(59330);
}



//Check for missing @media print { ... } rules in skin stylesheets.
//Please note: this was backpatched from 9.6 to 9.5, however is safe to run multiple times.
if (ze\dbAdm::needRevision(59600)) {
	
	
	$printFiles = [];
	
	foreach (\ze\row::getValues('skins', ['id', 'name', 'display_name'], ['missing' => 0]) as $skin) {
		$printFile = \ze\skin::path($skin['name']). 'editable_css/print.css';
		
		if (file_exists(CMS_ROOT. $printFile)) {
			if ($css = file_get_contents(CMS_ROOT. $printFile)) {
				if (!preg_match('/@media\s+print\b/i', $css)) {
					if (is_writable(CMS_ROOT. $printFile)) {
						file_put_contents(CMS_ROOT. $printFile, "\n". '@media print {'. "\n\n". $css. "\n\n". '}'. "\n");
					} else {
						$printFiles[] = $printFile;
					}
				}
			}
		}
	}
	
	
	if (!empty($printFiles)) {
		$mrg = [
			'example' => $printFiles[0]
		];
		
		echo ze\admin::nzPhrase(
			'The print-stylesheet at [[example]] is missing its "@media print { ... }" rule. Please either add this, or make the file writable so this script can automatically add the rule.',
			'The print-stylesheet at [[example]] and 1 other are missing their "@media print { ... }" rule. Please either add this, or make the files writable so this script can automatically add the rule.',
			'The print-stylesheet at [[example]] and [[count]] others are missing their "@media print { ... }" rule. Please either add this, or make the files writable so this script can automatically add the rule.',
			count($printFiles) - 1,
			$mrg
		);
		exit;
	}
	
	ze\dbAdm::revision(59600);
}

//In 9.7, we enhanced AWS support to also allow extracting text from document content items by using AWS Textract.
//A setting was renamed and an additional setting to enable S3 was added. Make the necessary adjustments.
if (ze\dbAdm::needRevision(60020)) {
	if (ze\module::inc('zenario_ctype_document')) {
		$enableAwsSupport = ze::setting('aws_s3_support');
		ze\site::setSetting('enable_aws_support', $enableAwsSupport);
		ze\row::delete('site_settings', ['name' => 'aws_s3_support']);
		
		//There is a new setting "Allow document content items to be stored on AWS S3"
		//which must now be enabled to use AWS S3. Enable the new checkbox if the feature is in use.
		$awsS3Bucket = ze::setting('aws_s3_bucket');
		
		if ($awsS3Bucket) {
			ze\site::setSetting('allow_document_content_items_to_be_stored_on_aws_s3', true);
		}
	}
	
	ze\dbAdm::revision(60020);
}




//
//	Zenario 9.0
//

//Update Advanced Search module: replace "Resize and crop" with "Crop and zoom".
if (ze\dbAdm::needRevision(60620)) {
	if (ze\module::isRunning('zenario_advanced_search')) {
		$instances = ze\module::getModuleInstancesAndPluginSettings('zenario_advanced_search');
		
		foreach ($instances as $instance) {
			if (
				!empty($instance['settings']['search_html'])
				&& !empty($instance['settings']['html_show_feature_image'])
				&& !empty($instance['settings']['html_feature_image_canvas'])
				&& $instance['settings']['html_feature_image_canvas'] == 'resize_and_crop'
			) {
				ze\row::update('plugin_settings', ['value' => 'crop_and_zoom'], ['instance_id' => (int) $instance['instance_id'], 'egg_id' => (int) $instance['egg_id'], 'name' => 'html_feature_image_canvas']);
			}
			
			if (
				!empty($instance['settings']['search_document'])
				&& !empty($instance['settings']['document_show_feature_image'])
				&& !empty($instance['settings']['document_feature_image_canvas'])
				&& $instance['settings']['document_feature_image_canvas'] == 'resize_and_crop'
			) {
				ze\row::update('plugin_settings', ['value' => 'crop_and_zoom'], ['instance_id' => (int) $instance['instance_id'], 'egg_id' => (int) $instance['egg_id'], 'name' => 'document_feature_image_canvas']);
			}
			
			if (
				!empty($instance['settings']['search_news'])
				&& !empty($instance['settings']['news_show_feature_image'])
				&& !empty($instance['settings']['news_feature_image_canvas'])
				&& $instance['settings']['news_feature_image_canvas'] == 'resize_and_crop'
			) {
				ze\row::update('plugin_settings', ['value' => 'crop_and_zoom'], ['instance_id' => (int) $instance['instance_id'], 'egg_id' => (int) $instance['egg_id'], 'name' => 'news_feature_image_canvas']);
			}
			
			if (
				!empty($instance['settings']['search_blog'])
				&& !empty($instance['settings']['blog_show_feature_image'])
				&& !empty($instance['settings']['blog_feature_image_canvas'])
				&& $instance['settings']['blog_feature_image_canvas'] == 'resize_and_crop'
			) {
				ze\row::update('plugin_settings', ['value' => 'crop_and_zoom'], ['instance_id' => (int) $instance['instance_id'], 'egg_id' => (int) $instance['egg_id'], 'name' => 'blog_feature_image_canvas']);
			}
			
			if (
				!empty($instance['settings']['search_in_other_modules'])
				&& !empty($instance['settings']['other_module_show_image'])
				&& !empty($instance['settings']['other_module_image_canvas'])
				&& $instance['settings']['other_module_image_canvas'] == 'resize_and_crop'
			) {
				ze\row::update('plugin_settings', ['value' => 'crop_and_zoom'], ['instance_id' => (int) $instance['instance_id'], 'egg_id' => (int) $instance['egg_id'], 'name' => 'other_module_image_canvas']);
			}
		}
	}
	
	ze\dbAdm::revision(60620);
}

//In 10.0, we fixed a bug where if an image was used in a non-version controlled WYSIWYG editor,
//it would not be displayed as "In use by plugin X".
//This logic will update the usage info for existing plugins.
//It is adapted from ze\welcome::postInstallTasks().
if (ze\dbAdm::needRevision(60650)) {
	foreach (\ze\sql::select('
		SELECT id, content_id, `content_type`, content_version, is_nest, is_slideshow
		FROM '. DB_PREFIX. 'plugin_instances
		WHERE content_id = 0'
	) as $instance) {
		\ze\contentAdm::resyncLibraryPluginFiles($instance['id'], $instance);
	}
	
	ze\dbAdm::revision(60650);
}

//In 10.0, we changed the way the Feed Reader caches data
//and how often it tries to get the live data.
//This logic will clear up old data.
if (ze\dbAdm::needRevision(60662)) {
	if (ze\module::isRunning('zenario_feed_reader')) {
		$instances = ze\module::getModuleInstancesAndPluginSettings('zenario_feed_reader');
		
		foreach ($instances as $instance) {
			if ($instance['settings']['feed_source']) {
				$sql = "
					DELETE FROM " . DB_PREFIX . "plugin_instance_store
					WHERE instance_id = " . (int) $instance['instance_id'] . "
					AND use_by_time IS NULL";
				ze\sql::update($sql);
			}
		}
	}
	
	ze\dbAdm::revision(60662);
}

if (ze\dbAdm::needRevision(61087)) {
	if (ze\module::isRunning('zenario_ecommerce_manager')) {
		if ($sitemapContentItem = ze::setting('zenario_ecommerce_manager__xmp_sitemap_content_item')) {
			ze\site::setSetting('zenario_ecommerce_manager__xml_sitemap_content_item', $sitemapContentItem);
		}
		ze\row::delete('site_settings', ['name' => 'zenario_ecommerce_manager__xmp_sitemap_content_item']);
		
		if ($sitemapConductorState = ze::setting('zenario_ecommerce_manager__xmp_sitemap_conductor_state')) {
			ze\site::setSetting('zenario_ecommerce_manager__xml_sitemap_conductor_state', $sitemapConductorState);
		}
		ze\row::delete('site_settings', ['name' => 'zenario_ecommerce_manager__xmp_sitemap_conductor_state']);
	}
	
	ze\dbAdm::revision(61087);
}




//
//	Zenario 10.1
//


//In Zenario 10.1, we're trying to use WebP images rather than PNG or JPEG.
//Try to go through any WYSIWYG Editor content and switch any links to public images from using 
//PNG or JPEG to using WebP if possible.
if (ze\dbAdm::needRevision(61230)) {
	set_time_limit(60 * 10);
	\ze\fileAdm::updateAllImagePublicLinks();
	
	ze\dbAdm::revision(61230);
}


//In 10.1 we've changed the format of the directory structure in the docstore directory.
//Previously files were stored in directories the format "filename_checksum/"
//Now we want to start using subdirectories in the format "usage/filename-shortchecksum/".
//Technically it would be posible not to move the files, but for neatness we'll try and migrate them.
//Oh, and while we're at it, we're also going to combine this update with some changes for how hierarchical files are handled.
if (ze\dbAdm::needRevision(61616)) {
	
	$docStoreDir = \ze::setting('docstore_dir'). '/';
	$junkDir = \ze::setting('docstore_dir'). '/_uncategorised/';
	$delMeDir = \ze::setting('docstore_dir'). '/_delete_me/';
	
	//Only run this step if the docstore directory is defined and writable.
	if (is_dir($docStoreDir)
	 && is_writable($docStoreDir)) {
		
		//Look through all of the files in the files table that are stored in the docstore.
		$sql = "
			SELECT f.id, f.filename, f.short_checksum, f.path, f.usage, GROUP_CONCAT(g.id) AS `identical_ids`
			FROM ". DB_PREFIX. "files AS f
			LEFT JOIN ". DB_PREFIX. "files AS g
			   ON f.checksum = g.checksum AND f.id != g.id
			WHERE f.location = 'docstore'
			GROUP BY f.id, f.filename, f.path, f.`usage`";
		
		//N.b. the logic below won't work unless this query is run and then the results stored in memory,
		//but thankfully that's the default option!
		foreach (ze\sql::select($sql) as $file) {
			
			$copyFile = false;
			$moveFileIntoDB = false;
			
			//We'll want to move the file into the database if it is a hierarchical_file_thumbnail. We don't want these in the docstore any more.
			if ($file['usage'] == 'hierarchical_file_thumbnail') {
				$moveFileIntoDB = true;
			
			//Anything in the "hierarchial_file" directory will need to be moved to fix that spelling mistake!
			} elseif (false !== ze\ring::chopPrefix('hierarchial_file/', $file['path'])) {
			
			//Anything that's already in the correct format can be skipped.
			//Note: This line is here because we want to write this script to be safe if called multiple times.
			} elseif (false !== ze\ring::chopPrefix($file['usage']. '/', $file['path'])) {
				continue;
			}
			
			//See if the file is actually there.
			$oldFilePath = ze\file::docstorePath($file['path'], false);
			
			//If it's not, write a paranoia check to see if it was accidentally moved to the "_uncategorised/" subdirectory
			//by a previous run of this script. Fish it out of there in that case!
			if ($oldFilePath === false) {
				$oldFilePath = ze\file::docstorePath($file['path'], false, $junkDir);
			}
			
			//Still can't find the file?
			//If an identicle file was uploaded in two different buckets, the old system only kept one copy.
			//So it might have already been moved
			if ($oldFilePath === false) {
				if (!empty($file['identical_ids'])) {
					foreach (ze\ray::explodeAndTrim($file['identical_ids'], true) as $otherFileId) {
						$otherFilePath = ze\file::docstorePath($otherFileId, false);
						
						if ($otherFilePath !== false) {
							$oldFilePath = $otherFilePath;
							$copyFile = true;
							break;
						}
					}
				}
			}
			
			//Catch the case where someone is restoring an old backup but we've already run
			//this script before.
			if ($oldFilePath === false) {
				$path = ze\fileAdm::docstoreDirPath($file);
				$newFilePath = ze\file::docstorePath($path, false);
				
				if ($newFilePath !== false) {
					ze\row::update('files', ['path' => $path], $file['id']);
					continue;
				}
			}
			
			//Catch a similar case, where someone is restoring an old backup where the file was
			//previously moved to the "_delete_me/" subdirectory.
			if ($oldFilePath === false && $moveFileIntoDB) {
				$oldFilePath = ze\file::docstorePath($file['path'], false, $delMeDir);
				
				//N.b. no need to move the file into the "_delete_me/" subdirectory if it's already in there.
				if ($oldFilePath !== false) {
					$copyFile = true;
				}
			}
			
			//If we couldn't find it we'll just have to give up on it and abandon it as missing
			if ($oldFilePath === false) {
				continue;
			}
			
			
			if ($moveFileIntoDB) {
				//Move the file into the database if needed
				\ze\row::update('files', [
					'location' => 'db',
					'path' => '',
					'data' => file_get_contents($oldFilePath)
				], $file['id']);
				
				//Move the old directory into a "delete me" directory
				if (!$copyFile) {
					//Create the "_delete_me/" subdirectory if it's not there already
					if (!is_dir($delMeDir)) {
						if (!(mkdir($delMeDir) && \ze\cache::chmod($delMeDir, 0777))) {
							break;
						}
					}
					if (!is_writable($delMeDir)) {
						break;
					}
					
					$existingDir = dirname($oldFilePath);
					$subDir = basename($existingDir);
					rename($existingDir, $delMeDir. '/'. $subDir);
				}
				
			} else {
				//Try and make a new directory for the file in the new format
				$path = $newFilePath = false;
				if (ze\fileAdm::createDocstoreDir($file, $path, $newFilePath)) {
					if ($copyFile) {
						copy($oldFilePath, $newFilePath);
					} else {
						rename($oldFilePath, $newFilePath);
					}
					\ze\cache::chmod($newFilePath, 0666);
					
					ze\row::update('files', ['path' => $path], $file['id']);
					
					
					//Remove the old directory when we're done.
					if (!$copyFile) {
						ze\cache::deleteDir(dirname($oldFilePath));
					}
				}
			}
		}
		
		//Scan the docstore directory and add any lose file that's not been claimed into the
		//"_uncategorised/" subdirectory
		foreach (scandir($docStoreDir) as $subDir) { 
			if ($subDir == '.'
			 || $subDir == '..') {
				continue;
			}
			
			$existingDir = $docStoreDir. '/'. $subDir;
			if (!is_writable($existingDir)) {
				continue;
			}
			
			//Look for any subdirectory that's in the old format
			if (is_numeric($subDir)
			 || preg_match('@_\w*_[a-zA-Z0-9_-]{20,25}$@', $subDir)) {
				
				//Create the "_uncategorised/" subdirectory if it's not there already
				if (!is_dir($junkDir)) {
					if (!(mkdir($junkDir) && \ze\cache::chmod($junkDir, 0777))) {
						break;
					}
				}
				if (!is_writable($junkDir)) {
					break;
				}
				
				rename($existingDir, $junkDir. '/'. $subDir);
			}
		}
		
		//Clear out the "public/downloads/" directory as the symlinks may be out of date now.
		$publicDownloadsDir = CMS_ROOT. 'public/downloads/';
		if (is_dir($publicDownloadsDir)
		 && is_writable($publicDownloadsDir)) {
			
			ze\cache::deleteDir($publicDownloadsDir, 1, $deleteSymlinks = true);
			
			//...then try and regenerate it if we can
			$errors = $exampleFile = false;
			ze\document::checkAllPublicLinks($forceRemake = true, $errors, $exampleFile);
		}
		
		//If the "document_thumbnail" or "hierarchial_file" directories were ever created, try to remove them.
		//But only if they don't have directories inside them.
		foreach ([$docStoreDir. 'document_thumbnail/', $docStoreDir. 'hierarchial_file/'] as $unwantedDir) {
			if (is_dir($unwantedDir)
			 && is_writable($unwantedDir)) {
				ze\cache::deleteDir($unwantedDir);
			}
		}
	}
	
	ze\dbAdm::revision(61616);
}

//In 10.1, we changed the E-Commerce system and as a result, a site setting is no longer needed.
//Remove it now.
if (ze\dbAdm::needRevision(61625)) {
	if (ze\module::isRunning('zenario_ecommerce_manager')) {
		ze\row::delete('site_settings', ['name' => 'zenario_ecommerce_manager__dataset_field_use_separate_address_for_delivery']);
	}
	
	ze\dbAdm::revision(61625);
}




//
//	Zenario 10.2
//


//In 10.2 we've moved a few standard phrases to be code based phrases.
//There's an update (also revision 63270) in step 2 to migrate them if you've previously created them.
//However if you've not previously created them we'll need to create them by re-importing the CSV files.
if (ze\dbAdm::needRevision(63270)) {
	\ze\contentAdm::importPhrasesForModule('zenario_common_features', $langId = false, $keepExistingTranslations = true);
	
	ze\dbAdm::revision(63270);
}




//
//	Zenario 10.3
//



//In 10.3, we removed the setting to send a delayed registration email.
//Clean up the old site settings.
if (ze\dbAdm::needRevision(63552)) {
	$loopThrough = [
		'send_delayed_registration_email',
		'delayed_registration_email_template',
		'delayed_registration_email_days_delayed',
		'delayed_registration_email_time_of_day'
	];
	
	foreach ($loopThrough as $settingName) {
		ze\row::delete('site_settings', ['name' => $settingName]);
	}
	
	ze\dbAdm::revision(63552);
}

//In 10.3, we added code to prevent adding special pages to the searchable cache table.
//Remove any existing special pages from the table now.
if (ze\dbAdm::needRevision(63560)) {
	$contentItems = ze\row::getArray('content_items_searchable_cache', ['content_id', 'content_type', 'content_version', 'content_tag'], [], ['content_type', 'content_id']);
	
	if ($contentItems && is_array($contentItems) && count($contentItems) > 0) {
		$specialPagesInSearchableCacheTable = [];
		
		foreach ($contentItems as $contentItem) {
			if (!\ze\contentAdm::contentItemIsSearchable($contentItem['content_id'], $contentItem['content_type'], $contentItem['content_version'])) {
				$specialPagesInSearchableCacheTable[] = $contentItem['content_tag'];
			}
		}
		
		if (count($specialPagesInSearchableCacheTable) > 0) {
			$sql = "
				DELETE FROM " . DB_PREFIX . "content_items_searchable_cache
				WHERE `content_tag` IN(" . ze\escape::in($specialPagesInSearchableCacheTable) . ")";
			ze\sql::update($sql);
		}
	}
	
	ze\dbAdm::revision(63560);
}


//T13130, MIC images should be stored in their own folder inside public/ directory
//MiC images use slightly different different logic to regular images.
//From version 10.3 onwards, we're going to be putting them in a different directory to regular images
//to prevent bugs and issues caused when the same image is used in both places.
if (ze\dbAdm::needRevision(63590)) {
	
	//Look through the MIC images recorded in the database.
	//We'll want to check if these are in the public/images/ directory and remove them if so.
	//However exclude any images where there is also an identical image in the image library.
	//Those can stay in the public/images/ directory as the images system will be using them.
	$sql = "
		SELECT mi.mime_type, mi.short_checksum, 'image' AS `usage`
		FROM ". DB_PREFIX. "files AS mi
		LEFT JOIN ". DB_PREFIX. "files AS fi
		   ON fi.short_checksum = mi.short_checksum
		  AND fi.usage = 'image'
		  AND fi.privacy = 'public'
		WHERE mi.usage = 'mic'
		  AND fi.short_checksum IS NULL";
	
	foreach (ze\sql::select($sql) as $image) {
		ze\file::deletePublicImage($image);
	}
	
	ze\dbAdm::revision(63590);
}

//In 10.2, we renamed the table content_cache to content_items_searchable_cache. We also added a few new columns to it.
//In 10.3, we added more columns to it.
//Populate the table with the relevant data.
if (ze\dbAdm::needRevision(63595)) {
	$sql = "
		SELECT id AS cID, type AS cType, visitor_version AS cVersion, tag_id AS cTag
		FROM " . DB_PREFIX . "content_items
		WHERE status IN('published', 'published_with_draft')";
	$result = \ze\sql::select($sql);
	
	while ($row = \ze\sql::fetchAssoc($result)) {
		//Put the content info in the content_items_searchable_cache table
		ze\contentAdm::updateContentItemCache($row['cID'], $row['cType'], $row['cVersion']);
		ze\fileAdm::updateDocumentContentItemExtract($row['cID'], $row['cType'], $row['cVersion']);
	}
	
	ze\dbAdm::revision(63595);
}

//In 10.3, we removed the setting to send a delayed registration email.
//Old settings were cleaned up in rev 63552.
//Now remove the scheduled task and any logs.
if (ze\dbAdm::needRevision(63597)) {
	$sql = "
		DELETE j.*, jl.*
		FROM ". DB_PREFIX. "jobs AS j
		INNER JOIN ". DB_PREFIX. "job_logs AS jl
		   ON jl.job_id = j.id
		WHERE j.job_name = ?";
	$statement = ze\sql::prepare($sql, 'a');
	$statement->update(['jobSendDelayedRegistrationEmails']);
	
	ze\dbAdm::revision(63597);
}

//In 10.3, the menu_text table now has a redundant column with info on the content item that row is linked to,
//to help make certain queries on the menu more efficient.
//These will need to be populated.
if (ze\dbAdm::needRevision(63680)) {
	$sql = "
		SELECT id, type
		FROM ". DB_PREFIX. "content_items";
	foreach (ze\sql::select($sql) as $content) {
		ze\contentAdm::syncMenuTextStatus($content['id'], $content['type']);
	}
	
	ze\dbAdm::revision(63680);
}

//In 10.3, we removed Dropbox support. Clean up the API key setting.
if (ze\dbAdm::needRevision(63760)) {
	ze\row::delete('site_settings', ['name' => 'dropbox_api_key']);
	
	ze\dbAdm::revision(63760);
}

//In 10.3, we removed the feature to set a background image
//on a content item or layout. Sort out any possible leftover images:
//attempt to move them to the image library. Delete duplicates.
if (ze\dbAdm::needRevision(63785)) {
	if (ze\row::exists('files', ['usage' => 'background_image'])) {
		$backgroundImages = ze\row::getArray('files', ['id', 'checksum'], ['usage' => 'background_image']);
		
		//If this image already exists in the image library,
		//just delete the leftover background image.
		//Otherwise move it to the image library.
		
		foreach ($backgroundImages as $backgroundImage) {
			if (!ze\row::exists('files', ['checksum' => $backgroundImage['checksum'], 'usage' => 'image'])) {
				ze\fileAdm::copyInDatabase('image', $backgroundImage['id']);
			}
			
			ze\fileAdm::delete($backgroundImage['id']);
		}
	}
	
	ze\dbAdm::revision(63785);
}

//In 10.3, we moved a site setting from 1 tab to another
//and made it dependent on a checkbox. Check the checkbox
//if the setting was previously populated.
if (ze\dbAdm::needRevision(64130)) {
	if (ze::setting('default_icon')) {
		ze\site::setSetting('enable_og_tags_for_public_pages', 1);
	}
	
	ze\dbAdm::revision(64130);
}

//Automatically turn on the "base64" encoding option for emails in this update,
//as the "automatic" option can have compatibility issues.
//Note: this was backpatched from 10.4 to 10.3, but is safe to run multiple times.
if (ze\dbAdm::needRevision(64700)) {
	ze\site::setSetting('base64_encode_emails', 1);
	
	ze\dbAdm::revision(64700);
}

//Combined update that fixes/resyncs links to images in email templates.
//Try to replace links to file.php with links to the actual files in the public/images/ directory.
//Also try to replace links to WebP images with links to the original JPEG/PNG versions.
//Note: this was backpatched from 10.4 to 10.3, but is safe to run multiple times.
if (ze\dbAdm::needRevision(64740)) {
	
	ze\fileAdm::updateAllImagePublicLinksInEmailTemplates();

	ze\dbAdm::revision(64740);
}
