<?php /*

 Composr
 Copyright (c) Christopher Graham, 2004-2024

 See docs/LICENSE.md for full licensing information.


 NOTE TO PROGRAMMERS:
   Do not edit this file. If you need to make changes, save your changed file to the appropriate *_custom folder
   **** If you ignore this advice, then your website upgrades (e.g. for bug fixes) will likely kill your changes ****

*/

/**
 * @license    http://opensource.org/licenses/cpal_1.0 Common Public Attribution License
 * @copyright  Christopher Graham
 * @package    core_themeing
 */

/**
 * Compile all templates to improve run-time performance.
 */
function compile_all_templates()
{
    require_code('web_resources2');
    require_code('web_resources');

    require_all_lang();

    $themes = [
        'admin',
        $GLOBALS['FORUM_DRIVER']->get_theme(''),
    ];

    $directories = [
        'css' => 'css',
        'javascript' => 'js',
        'templates' => 'tpl',
        'text' => 'txt',
        'xml' => 'xml',
        'css_custom' => 'css',
        'javascript_custom' => 'js',
        'templates_custom' => 'tpl',
        'text_custom' => 'txt',
        'xml_custom' => 'xml',
    ];

    $base_path = get_file_base() . '/themes';

    $lang = user_lang();

    push_query_limiting(false);

    require_code('files2');

    global $USER_THEME_CACHE;

    $max_memory = 28 * 1024 * 1024;

    @ignore_user_abort(false);

    foreach ($themes as $theme) {
        $USER_THEME_CACHE = $theme; // FUDGE: due to {$THEME} in global.css, and some templates (it's considered static, as it's tied to cache directory)

        foreach ($directories as $directory => $suffix) {
            $dh = opendir($base_path . '/default/' . $directory);
            while (($file = readdir($dh)) !== false) {
                $parts = explode('.', $file, 2);

                if ((count($parts) == 2) && ($parts[1] == $suffix)) {
                    $codename = $parts[0];

                    if (substr($codename, 0, 1) == '_') {
                        continue;
                    }

                    $tcp_path = $base_path . '/' . $theme . '/templates_cached/' . $lang . '/' . $codename . '.' . $suffix . '.tcp';

                    if (!is_file($tcp_path)) {
                        $old_limit = cms_set_time_limit(5);

                        do_template($codename, [], $lang, true, null, '.' . $suffix, $directory, $theme);

                        switch ($directory) {
                            case 'javascript':
                            case 'javascript_custom':
                                javascript_enforce($codename, $theme);
                                break;

                            case 'css':
                            case 'css_custom':
                                css_enforce($codename, $theme);
                                break;
                        }

                        cms_set_time_limit($old_limit);

                        if (memory_get_usage() > $max_memory) {
                            closedir($dh);
                            unset($USER_THEME_CACHE);
                            return;
                        }
                    }
                }
            }

            closedir($dh);
        }
    }

    unset($USER_THEME_CACHE);

    // Do language files too, for kicks...

    require_all_lang();
    require_all_open_lang_files();
}

/**
 * Add a theme.
 *
 * @param  ID_TEXT $name The theme name
 * @param  boolean $include_themeini Whether to create theme.ini
 */
function actual_add_theme(string $name, bool $include_themeini = true)
{
    push_query_limiting(false);

    if ((file_exists(get_custom_file_base() . '/themes/' . $name)) || ($name == 'default')) {
        warn_exit(do_lang_tempcode('ALREADY_EXISTS', escape_html($name)));
    }

    require_code('abstract_file_manager');
    force_have_afm_details(['themes']);

    // Create directories
    $dir_list = [
        '',
        'images',
        'images/logo',
        'images_custom',
        'templates',
        'templates_custom',
        'javascript',
        'javascript_custom',
        'xml',
        'xml_custom',
        'text',
        'text_custom',
        'templates_cached',
        'css',
        'css_custom',
    ];
    $langs = find_all_langs(true);
    foreach (array_keys($langs) as $lang) {
        $dir_list[] = 'templates_cached/' . $lang;
    }
    $dir_list_access = ['', 'images', 'images_custom', 'css'];
    foreach ($dir_list as $dir) {
        $path = 'themes/' . $name . '/' . $dir;
        afm_make_directory($path, true);
        $path = 'themes/' . $name . '/' . (($dir == '') ? '' : ($dir . '/')) . 'index.html';
        if (file_exists(get_file_base() . '/themes/default/' . (($dir == '') ? '' : ($dir . '/')) . 'index.html')) {
            afm_copy('themes/default/' . (($dir == '') ? '' : ($dir . '/')) . 'index.html', $path, false);
        }
        $path = 'themes/' . $name . '/' . (($dir == '') ? '' : ($dir . '/')) . '.htaccess';
        if (file_exists(get_file_base() . '/themes/default/' . (($dir == '') ? '' : ($dir . '/')) . '.htaccess')) {
            afm_copy('themes/default/' . (($dir == '') ? '' : ($dir . '/')) . '.htaccess', $path, false);
        }
    }
    if ($include_themeini) {
        afm_copy('themes/default/theme.ini', 'themes/' . $name . '/theme.ini', true);
    }

    // Copy image references from default
    $start = 0;
    do {
        $theme_images = $GLOBALS['SITE_DB']->query_select('theme_images', ['*'], ['theme' => 'default'], '', 100, $start);
        foreach ($theme_images as $theme_image) {
            $test = $GLOBALS['SITE_DB']->query_select_value_if_there('theme_images', 'id', ['theme' => $name, 'id' => $theme_image['id'], 'lang' => $theme_image['lang']]);
            if ($test === null) {
                $GLOBALS['SITE_DB']->query_insert('theme_images', ['id' => $theme_image['id'], 'theme' => $name, 'url' => $theme_image['url'], 'lang' => $theme_image['lang']]);
            }
        }
        $start += 100;
    } while (!empty($theme_images));

    Self_learning_cache::erase_smart_cache();

    require_lang('themes');
    log_it('ADD_THEME', $name);
}

/**
 * Rename a theme.
 *
 * @param  ID_TEXT $theme The original theme name
 * @param  ID_TEXT $to The new theme name
 */
function actual_rename_theme(string $theme, string $to)
{
    if ($theme == 'default') {
        fatal_exit(do_lang_tempcode('INTERNAL_ERROR'));
    }

    if ((file_exists(get_custom_file_base() . '/themes/' . $to)) || ($to == 'default' || $to == 'admin')) {
        warn_exit(do_lang_tempcode('ALREADY_EXISTS', escape_html($to)));
    }

    global $USER_THEME_CACHE;
    if (($USER_THEME_CACHE !== null) && ($USER_THEME_CACHE == $theme)) {
        $USER_THEME_CACHE = $to;
    }

    require_code('abstract_file_manager');
    force_have_afm_details(['themes', 'themes/' . $theme]);
    afm_move('themes/' . $theme, 'themes/' . $to);

    $GLOBALS['SITE_DB']->query_update('theme_images', ['theme' => $to], ['theme' => $theme]);
    $theme_images = $GLOBALS['SITE_DB']->query('SELECT url FROM ' . $GLOBALS['SITE_DB']->get_table_prefix() . 'theme_images WHERE url LIKE \'themes/' . db_encode_like($theme) . '/%\'');
    foreach ($theme_images as $image) {
        $new_url = str_replace('themes/' . $theme . '/', 'themes/' . $to . '/', $image['url']);
        $GLOBALS['SITE_DB']->query_update('theme_images', ['url' => $new_url], ['url' => $image['url']], '', 1);
    }
    if (get_forum_type() == 'cns') {
        $GLOBALS['FORUM_DB']->query_update('f_members', ['m_theme' => $to], ['m_theme' => $theme]);
    }
    $GLOBALS['SITE_DB']->query_update('zones', ['zone_theme' => $to], ['zone_theme' => $theme]);

    require_lang('themes');
    log_it('RENAME_THEME', $theme, $to);
}

/**
 * Copy a theme.
 *
 * @param  ID_TEXT $theme The original theme name
 * @param  ID_TEXT $to The copy's theme name
 * @param  array $theme_images_to_skip List of theme images to skip (presumably as they'll be created separately)
 * @param  array $css_files_to_skip List of CSS files to skip (presumably as they'll be created separately)
 * @param  boolean $include_themeini Whether to create theme.ini
 */
function actual_copy_theme(string $theme, string $to, array $theme_images_to_skip = [], array $css_files_to_skip = [], bool $include_themeini = true)
{
    if ($theme == 'default') {
        fatal_exit(do_lang_tempcode('INTERNAL_ERROR'));
    }

    $theme_images_to_skip_path_map = [];
    foreach ($theme_images_to_skip as $theme_image => $image_is_multi_lang) {
        $path = get_custom_file_base() . '/' . urldecode(find_theme_image($theme_image, false, false, $theme));
        if (!is_file($path)) {
            $path = get_file_base() . '/' . urldecode(find_theme_image($theme_image, false, false, $theme));
        }
        $theme_images_to_skip_path_map[$path] = $theme_image;
    }
    $theme_images_to_skip_flipped = array_keys($theme_images_to_skip);
    $css_files_to_skip_flipped = array_keys($css_files_to_skip);

    if ((file_exists(get_custom_file_base() . '/themes/' . $to)) || ($to == 'default' || $to == 'admin')) {
        warn_exit(do_lang_tempcode('ALREADY_EXISTS', escape_html($to)));
    }

    require_code('abstract_file_manager');
    require_code('files2');

    force_have_afm_details(['themes']);

    // Create skeleton
    $needed = [
        'css',
        'css_custom',
        'images',
        'images_custom',
        'templates',
        'templates_cached/' . get_site_default_lang(),
        'templates_custom',
        'javascript_custom',
        'xml_custom',
        'text_custom',
    ];
    foreach ($needed as $n) {
        afm_make_directory(dirname('themes/' . $to . '/' . $n), true, true);
    }

    // Copy files
    global $THEME_IMAGE_EXTENSIONS;
    $matches = [];
    $contents = get_directory_contents(get_custom_file_base() . '/themes/' . $theme, '', null);
    foreach ($contents as $file) {
        // Skip files as requested
        if ((preg_match('#^images(_custom)?/(.*)\.(' . implode('|', $THEME_IMAGE_EXTENSIONS) . ')$#', $file, $matches) != 0) && (isset($theme_images_to_skip_path_map['themes/' . $theme . '/' . $file]))) {
            continue;
        }

        $matches = [];
        if (preg_match('#^css(_custom)?/(\w+)\.css$#', $file, $matches) != 0) {
            $match_key = count($matches) - 1;
            $file_key = $matches[$match_key] . '.css';
            if (in_array($file_key, $css_files_to_skip_flipped)) {
                continue;
            }
        }

        if (($file == 'theme.ini') && (!$include_themeini)) {
            continue;
        }

        afm_make_directory(dirname('themes/' . $to . '/' . $file), true, true);
        afm_copy('themes/' . $theme . '/' . $file, 'themes/' . $to . '/' . $file, true);
    }

    // Set theme image records
    $images = $GLOBALS['SITE_DB']->query_select('theme_images', ['*'], ['theme' => $theme]);
    foreach ($images as $image) {
        // Skip records as requested
        if (isset($theme_images_to_skip_flipped[$image['id']])) {
            continue;
        }

        $image['theme'] = $to;
        $image['url'] = preg_replace('#^themes/' . $theme . '/#', 'themes/' . $to . '/', $image['url']);
        $GLOBALS['SITE_DB']->query_insert('theme_images', $image, false, true); // errors suppressed in case already there
    }

    Self_learning_cache::erase_smart_cache();

    require_lang('themes');
    log_it('COPY_THEME', $theme, $to);
}

/**
 * Delete a theme.
 *
 * @param  ID_TEXT $theme The theme name
 */
function actual_delete_theme(string $theme)
{
    if ($theme == 'default') {
        fatal_exit(do_lang_tempcode('INTERNAL_ERROR'));
    }

    global $USER_THEME_CACHE;
    if (($USER_THEME_CACHE !== null) && ($USER_THEME_CACHE == $theme)) {
        $USER_THEME_CACHE = 'default';
    }

    require_code('abstract_file_manager');
    force_have_afm_details(['themes/' . $theme]);
    afm_delete_directory('themes/' . $theme, true);

    $GLOBALS['SITE_DB']->query_delete('theme_images', ['theme' => $theme]);

    require_lang('themes');
    log_it('DELETE_THEME', $theme);
}

/**
 * Set the live theme.
 *
 * @param  SHORT_TEXT $theme_name The theme to make live
 */
function set_live_theme(string $theme_name)
{
    $GLOBALS['SITE_DB']->query('UPDATE ' . get_table_prefix() . 'zones SET zone_theme=\'' . db_escape_string($theme_name) . '\' WHERE ' . db_string_not_equal_to('zone_name', 'cms') . ' AND ' . db_string_not_equal_to('zone_name', 'adminzone'));
}

/**
 * AJAX script for rendering some Tempcode.
 */
function tempcode_tester_script()
{
    prepare_backend_response('text/plain');

    $tempcode = post_param_string('tempcode');

    $params = [];
    foreach ($_POST as $key => $val) {
        if ((substr($key, 0, 4) == 'key_') && ($val != '')) {
            $_key = str_replace('}', '', str_replace('{', '', post_param_string($key, '')));
            $_val = post_param_string('val_' . substr($key, 4), '');
            $params[$_key] = $_val;
        }
    }

    require_code('tempcode_compiler');
    $tpl = template_to_tempcode($tempcode);
    $bound = $tpl->bind($params, 'tempcode_tester');
    $out = $bound->evaluate();
    if (get_param_integer('comcode', 0) == 1) {
        echo static_evaluate_tempcode(comcode_to_tempcode($out));
    } else {
        echo $out;
    }
}

/**
 * Add a theme image.
 *
 * @param  ID_TEXT $theme The theme the theme image is in
 * @param  LANGUAGE_NAME $lang The language the theme image is for
 * @param  SHORT_TEXT $id The theme image ID
 * @param  URLPATH $url The URL to the theme image
 * @param  boolean $fail_ok Whether to allow failure without bombing out
 */
function actual_add_theme_image(string $theme, string $lang, string $id, string $url, bool $fail_ok = false)
{
    $test = $GLOBALS['SITE_DB']->query_select_value_if_there('theme_images', 'id', ['id' => $id, 'theme' => $theme, 'lang' => $lang]);
    if ($test !== null) {
        if ($fail_ok) {
            return;
        }
        warn_exit(do_lang_tempcode('ALREADY_EXISTS', escape_html($id)));
    }

    $GLOBALS['SITE_DB']->query_insert('theme_images', ['id' => $id, 'theme' => $theme, 'url' => $url, 'lang' => $lang]);

    require_lang('themes');
    log_it('ADD_THEME_IMAGE', $id, $theme);

    Self_learning_cache::erase_smart_cache();
}

/**
 * Edit a theme image.
 *
 * @param  SHORT_TEXT $old_id The current theme image ID
 * @param  ID_TEXT $theme The theme the theme image is in
 * @param  LANGUAGE_NAME $lang The language the theme image is for (blank: all languages)
 * @param  SHORT_TEXT $id The new theme image ID
 * @param  URLPATH $url The URL to the theme image
 * @param  boolean $quick Whether to avoid cleanup, etc
 */
function actual_edit_theme_image(string $old_id, string $theme, string $lang, string $id, string $url, bool $quick = false)
{
    if ($old_id != $id) {
        $where_map = ['theme' => $theme, 'id' => $id];
        if ($lang != '') {
            $where_map['lang'] = $lang;
        }
        $test = $GLOBALS['SITE_DB']->query_select_value_if_there('theme_images', 'id', $where_map);
        if ($test !== null) {
            warn_exit(do_lang_tempcode('ALREADY_EXISTS', escape_html($id)));
        }
    }

    if (!$quick) {
        $old_url = find_theme_image($id, true, true, $theme, ($lang == '') ? null : $lang);

        if (($old_url != $url) && ($old_url != '')) {
            if (($theme == 'default') || (strpos($old_url, 'themes/default/') === false)) {
                $where_map = ['theme' => $theme, 'id' => $id];
                if ($lang != '') {
                    $where_map['lang'] = $lang;
                }
                $GLOBALS['SITE_DB']->query_delete('theme_images', $where_map);

                cleanup_after_theme_image_file_removal($old_url);
            }
        }
    }

    if ($lang == '') {
        $langs = array_keys(find_all_langs());
    } else {
        $langs = [$lang];
    }

    $where_map = ['theme' => $theme, 'id' => $id];
    if ($lang != '') {
        $where_map['lang'] = $lang;
    }
    $GLOBALS['SITE_DB']->query_delete('theme_images', $where_map);

    foreach ($langs as $lang) {
        $GLOBALS['SITE_DB']->query_insert('theme_images', ['id' => $id, 'theme' => $theme, 'url' => $url, 'lang' => $lang], false, true); // errors suppressed in case already there
    }

    if (!$quick) {
        Self_learning_cache::erase_smart_cache();

        require_code('caches3');
        erase_cached_templates(false, null, TEMPLATE_DECACHE_WITH_THEME_IMAGE); // Paths may have been cached

        require_lang('themes');
        log_it('EDIT_THEME_IMAGE', $id, $theme);
    }
}

/**
 * Delete a theme image.
 *
 * @param  SHORT_TEXT $id The theme image ID
 * @param  ?ID_TEXT $theme The theme to delete in (null: all themes)
 * @param  ?LANGUAGE_NAME $lang The language to delete in (null: all languages) (blank: all languages)
 */
function actual_delete_theme_image(string $id, ?string $theme = null, ?string $lang = null)
{
    if ($theme !== null) {
        $old_url = find_theme_image($id, true, true, $theme, $lang);

        $where_map = ['theme' => $theme, 'id' => $id];
        if (($lang != '') && ($lang !== null)) {
            $where_map['lang'] = $lang;
        }
        $test = $GLOBALS['SITE_DB']->query_select_value_if_there('theme_images', 'url', $where_map);
        if ($test !== null) {
            $GLOBALS['SITE_DB']->query_delete('theme_images', ['id' => $id, 'url' => $test]);
        }
    } else {
        $old_url = find_theme_image($id, true, true);

        $GLOBALS['SITE_DB']->query_delete('theme_images', ['id' => $id]);
    }

    if ($old_url != '') {
        cleanup_after_theme_image_file_removal($old_url);
    }

    require_lang('themes');
    log_it('DELETE_THEME_IMAGE', $id, $theme);
}

/**
 * Export neatly named dump of all theme images for active theme.
 */
function export_theme_images()
{
    header('Content-Type: application/octet-stream');
    header('Content-Disposition: attachment; filename="theme_images.tar"');

    require_code('tar');
    require_code('files');
    $my_tar = tar_open('php://output', 'wb');
    $theme_images = $GLOBALS['SITE_DB']->query_select('theme_images', ['DISTINCT id']);
    foreach ($theme_images as $theme_image) {
        $path = rawurldecode(find_theme_image($theme_image['id'], true, true));
        if (($path != '') && (substr($path, 0, strlen('themes/default/images/')) != 'themes/default/images/')) {
            tar_add_file($my_tar, $theme_image['id'] . '.' . get_file_extension($path), $path, 0644, null, true);
        }
    }
    tar_close($my_tar);
}

/**
 * Regenerate all the theme image URLs in the database.
 *
 * @param  ID_TEXT $theme The theme we're searching in
 * @param  ?array $langs A map of languages (lang=>true) (null: find it in-function)
 * @param  ?ID_TEXT $target_theme The theme we're storing in (null: same as $theme)
 */
function regen_theme_images(string $theme, ?array $langs = null, ?string $target_theme = null)
{
    if ($langs === null) {
        $langs = find_all_langs(true);
    }
    if ($target_theme === null) {
        $target_theme = $theme;
    }

    $made_change = true;

    $images = array_merge(find_images_do_dir($theme, 'images/', $langs), find_images_do_dir($theme, 'images_custom/', $langs));

    foreach (array_keys($langs) as $lang) {
        $where = ['lang' => $lang, 'theme' => $target_theme];
        $existing = $GLOBALS['SITE_DB']->query_select('theme_images', ['id', 'url'], $where);

        // Cleanup broken references
        foreach ($existing as $e) {
            if ((!file_exists(get_custom_file_base() . '/' . rawurldecode($e['url']))) && (!file_exists(get_file_base() . '/' . rawurldecode($e['url'])))) {
                $GLOBALS['SITE_DB']->query_delete('theme_images', $e + $where, '', 1);
            }
        }

        // Add theme images for anything on disk but not currently having a reference
        foreach ($images as $id => $url) {
            $found = false;
            foreach ($existing as $e) {
                if (($e['url'] == $url) || ($e['id'] == $id)) {
                    $found = true;
                    break;
                }
            }

            if (!$found) {
                push_query_limiting(false);
                $correct_url = find_theme_image($id, false, true, $theme, $lang);
                $GLOBALS['SITE_DB']->query_insert('theme_images', ['id' => $id, 'lang' => $lang, 'theme' => $target_theme, 'url' => $correct_url], false, true); // race conditions
                pop_query_limiting();

                $made_change = false;
            }
        }
    }

    if ($made_change) {
        // Reset this so they can all load in in one go
        global $THEME_IMAGES_CACHE, $THEME_IMAGES_SMART_CACHE_LOAD;
        $THEME_IMAGES_CACHE = [];
        $THEME_IMAGES_SMART_CACHE_LOAD = 1;
    }

    Self_learning_cache::erase_smart_cache();
}

/**
 * Delete uploaded theme image if not tied into anything.
 * We have to work hard as it's conceivable the URL could be in the database in weird encoding schemes across multiple theme images.
 *
 * @param  URLPATH $old_url The URL to the theme image being deleted
 */
function cleanup_after_theme_image_file_removal(string $old_url)
{
    if (strpos($old_url, '/images_custom/') === false) {
        return;
    }

    $possible_paths = [];
    $possible_paths[] = rawurldecode($old_url);
    $possible_paths[] = urldecode($old_url);

    $possible_variations_in_db = $possible_paths;
    $possible_variations_in_db[] = $old_url;
    $possible_variations_in_db[] = cms_rawurlrecode($old_url);
    $possible_variations_in_db[] = cms_rawurlencode(rawurldecode($old_url));
    $possible_variations_in_db[] = rawurlencode(rawurldecode($old_url));
    $possible_variations_in_db[] = urlencode(rawurldecode($old_url));
    $possible_variations_in_db[] = cms_rawurlencode(urldecode($old_url));
    $possible_variations_in_db[] = rawurlencode(urldecode($old_url));
    $possible_variations_in_db[] = urlencode(urldecode($old_url));
    $possible_variations_in_db = array_unique($possible_variations_in_db);

    foreach ($possible_variations_in_db as $possibility) {
        $test = $GLOBALS['SITE_DB']->query_select_value_if_there('theme_images', 'id', ['url' => $possibility]);
        if ($test !== null) {
            return;
        }
    }

    foreach ($possible_paths as $path) {
        if (is_file(get_custom_file_base() . '/' . $path)) {
            @unlink(get_custom_file_base() . '/' . $path);
            sync_file(get_custom_file_base() . '/' . $path);
        }
    }
}

/**
 * The actualiser to generate an SVG sprite.
 *
 * @param  ID_TEXT $theme Theme
 * @param  boolean $monochrome Whether to generate the monochrome sprite
 * @param  boolean $userland Whether to include icons from the custom directory, and save the sprite in the custom directory
 * @return array A pair: Sprite path, list of icons added to sprite
 */
function generate_svg_sprite(string $theme, bool $monochrome, bool $userland) : array
{
    require_code('files2');
    require_code('xml');

    $icons_dir = 'icons' . ($monochrome ? '_monochrome' : '');

    $icon_paths = [];

    foreach (array_unique([$theme, 'default']) as $_theme) {
        foreach (array_unique([get_file_base(), get_custom_file_base()]) as $file_base) {
            $theme_icons_dir = $file_base . '/themes/' . $_theme . '/images/' . $icons_dir;
            if (file_exists($theme_icons_dir)) {
                $overriding_icon_paths = get_directory_contents($theme_icons_dir, $theme_icons_dir, IGNORE_ACCESS_CONTROLLERS, true, true, ['svg']);
                $icon_paths = _override_icon_paths($icon_paths, $overriding_icon_paths);
            }

            if ($userland) {
                $theme_custom_icons_dir = $file_base . '/themes/' . $_theme . '/images_custom/' . $icons_dir;
                if (file_exists($theme_custom_icons_dir)) {
                    $overriding_icon_paths = get_directory_contents($theme_custom_icons_dir, $theme_custom_icons_dir, IGNORE_ACCESS_CONTROLLERS, true, true, ['svg']);
                    $icon_paths = _override_icon_paths($icon_paths, $overriding_icon_paths);
                }
            }
        }
    }

    $base_path_regex = '^(' . preg_quote(get_custom_file_base(), '#') . '|' . preg_quote(get_file_base(), '#') . ')/themes/[\w\-]+/images(_custom)?/icons(_monochrome)?/';

    $old_icon_paths = $icon_paths;
    $icon_paths = [];
    foreach ($old_icon_paths as $icon_path) {
        $icon_name = substr(preg_replace('#' . $base_path_regex . '#', '', $icon_path), 0, -4);
        $icon_paths[$icon_name] = $icon_path;
    }

    ksort($icon_paths);

    $xml_namespaces = ['' => 'http://www.w3.org/2000/svg', 'xlink:' => 'http://www.w3.org/1999/xlink'];
    $skip_tags = ['http://www.w3.org/2000/svg:metadata'];
    $skip_xml_namespaces = ['http://www.inkscape.org/namespaces/inkscape', 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'];

    $output = '<' . '?xml version="1.0" encoding="utf-8"?' . '>' . "\n";
    $output .= '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">' . "\n";

    foreach ($icon_paths as $icon_name => $icon_path) {
        $xml = new CMS_simple_xml_reader(cms_file_get_contents_safe($icon_path, FILE_READ_LOCK | FILE_READ_BOM));
        if (isset($xml->gleamed[1]['viewBox'])) {
            $view_box = $xml->gleamed[1]['viewBox'];
        } else {
            $view_box = '0 0 ' . $xml->gleamed[1]['width'] . ' ' . $xml->gleamed[1]['height'];
        }
        $output .= '<symbol viewBox="' . $view_box . '" id="icon_' . str_replace('/', '__', $icon_name) . '">' . "\n";
        foreach ($xml->gleamed[3] as $child) {
            if (!is_array($child)) {
                // whitespace?
                continue;
            }

            // Skip empty <defs> elements
            if (($child[0] == 'http://www.w3.org/2000/svg:defs')) {
                $has_child_nodes = false;
                foreach ($child[3] as $_child) {
                    if (is_array($_child)) {
                        $has_child_nodes = true;
                    }
                }
                if (!$has_child_nodes) {
                    continue;
                }
            }

            $child_xml = $xml->pull_together([$child], $xml_namespaces, $skip_tags, $skip_xml_namespaces) . "\n";

            $output .= $child_xml;
        }
        $output .= "</symbol>\n";
    }

    $output .= "</svg>\n";

    $theme_image = $icons_dir . '_sprite';
    if ($userland) {
        $_sprite_path = 'themes/' . $theme . '/images_custom/' . $theme_image . '.svg';
        $sprite_path = get_custom_file_base() . '/' . $_sprite_path;
    } else {
        $_sprite_path = 'themes/' . $theme . '/images/' . $theme_image . '.svg';
        $sprite_path = get_file_base() . '/' . $_sprite_path;
    }

    cms_file_put_contents_safe($sprite_path, $output, FILE_WRITE_FIX_PERMISSIONS | FILE_WRITE_SYNC_FILE);

    $icons_added = array_keys($icon_paths);

    if ($userland) {
        $GLOBALS['SITE_DB']->query_delete('theme_images', ['id' => $theme_image]);
        actual_edit_theme_image($theme_image, $theme, '', $theme_image, $_sprite_path, false);
    }

    return [$_sprite_path, $icons_added];
}

/**
 * Override icon paths.
 *
 * @param  array $icon_paths Icon paths
 * @param  array $overriding_icon_paths Overriding icon paths
 * @return array
 */
function _override_icon_paths(array $icon_paths, array $overriding_icon_paths) : array
{
    $base_path_regex = '^(' . preg_quote(get_custom_file_base(), '#') . '|' . preg_quote(get_file_base(), '#') . ')/themes/[\w\-]+/images(_custom)?/icons(_monochrome)?/';

    foreach ($overriding_icon_paths as $overriding_icon_path) {
        $overriding_icon_name = preg_replace('#' . $base_path_regex . '#', '', $overriding_icon_path);
        $icon_paths = array_filter($icon_paths, function ($icon_path) use ($base_path_regex, $overriding_icon_name) {
            // Remove paths for icons that exist in $overriding_icon_paths
            return preg_match('#' . $base_path_regex . preg_quote($overriding_icon_name, '#') . '#', $icon_path) === 0;
        });
    }

    return array_merge($icon_paths, $overriding_icon_paths);
}

/**
 * Generate a new theme name for the new site name.
 *
 * @param  string $name The new site name
 * @return string The new theme name
 */
function generate_theme_name(string $name) : string
{
    $i = 1;
    do {
        $name = substr(preg_replace('#[^' . URL_CONTENT_REGEXP . ']#', '_', $name), 0, 40);
        if ($i > 1) {
            $name .= '_' . strval($i);
        }
        $i++;
    } while (file_exists(get_custom_file_base() . '/themes/' . $name));
    return $name;
}
