Update to SMF 2.1.4 - Installation Instructions for 2.1.3

Update to SMF 2.1.4
SMF 2.1.4

SMF 2.1.4 includes various bug fixes and security updates.

Note: This patch includes changes to Post.template.php.  If you have a mod that significantly modifies Post.template.php, it may need to be deinstalled first, and updated with a current version after applying this patch.

File Edits ALT + Click to collapse all the operations

./LICENSE

This operation isn't vital to the installation of this mod.
Find: Select
Copyright © 2011 Simple Machines. All rights reserved.
Replace With: Select
Copyright © 2023 Simple Machines. All rights reserved.
This operation isn't vital to the installation of this mod.
Find: Select
http://www.simplemachines.org
Replace With: Select
https://www.simplemachines.org

./SSI.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
define('SMF_VERSION', '2.1.3');
Replace With: Select
define('SMF_VERSION', '2.1.4');
Find: Select
define('SMF_SOFTWARE_YEAR', '2022');
define('JQUERY_VERSION', '3.6.0');
Replace With: Select
define('SMF_SOFTWARE_YEAR', '2023');
define('JQUERY_VERSION', '3.6.3');
Find: Select
mf.poster_time, mf.subject, ml.id_topic, mf.id_member, ml.id_msg, t.num_replies, t.num_views, mg.online_color, t.id_last_msg,
COALESCE(mem.real_name, mf.poster_name) AS poster_name, ' . ($user_info['is_guest'] ? '1 AS is_read, 0 AS new_from' : '
COALESCE(lt.id_msg, lmr.id_msg, 0) >= ml.id_msg_modified AS is_read,
COALESCE(lt.id_msg, lmr.id_msg, -1) + 1 AS new_from') . ', SUBSTRING(mf.body, 1, 384) AS body, mf.smileys_enabled, mf.icon
FROM {db_prefix}topics AS t
INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_last_msg)
LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mf.id_member)' . (!$user_info['is_guest'] ? '
LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
Replace With: Select
ml.poster_time, mf.subject, mf.id_topic, ml.id_member, ml.id_msg, t.num_replies, t.num_views, mg.online_color, t.id_last_msg,
COALESCE(mem.real_name, ml.poster_name) AS poster_name, ' . ($user_info['is_guest'] ? '1 AS is_read, 0 AS new_from' : '
COALESCE(lt.id_msg, lmr.id_msg, 0) >= ml.id_msg_modified AS is_read,
COALESCE(lt.id_msg, lmr.id_msg, -1) + 1 AS new_from') . ', SUBSTRING(ml.body, 1, 384) AS body, ml.smileys_enabled, ml.icon
FROM {db_prefix}topics AS t
INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
INNER JOIN {db_prefix}messages AS mf ON (mf.id_msg = t.id_first_msg)
LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ml.id_member)' . (!$user_info['is_guest'] ? '
LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
Find: Select
<input type="hidden" name="advanced" value="0"><input type="text" name="ssi_search" size="30"> <input type="submit" value="', $txt['search'], '" class="button">
Replace With: Select
<input type="hidden" name="advanced" value="0"><input type="text" name="search" size="30"> <input type="submit" value="', $txt['search'], '" class="button">

./Sources/BoardIndex.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
// Allow mods to add additional buttons here
Add Before: Select
// Replace the collapse and expand default alts.
addJavaScriptVar('smf_expandAlt', $txt['show_category'], true);
addJavaScriptVar('smf_collapseAlt', $txt['hide_category'], true);

./Sources/Cache/APIs/FileBased.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
if (file_exists($file) && ($fp = fopen($file, 'rb')) !== false)
Replace With: Select
if (($fp = @fopen($file, 'rb')) !== false)

./Sources/DbPackages-mysql.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
* Change a column.
Replace With: Select
* Change a column. You only need to specify the column attributes that are changing.
Find: Select
* @param array $column_info An array of info about the "new" column definition (see {@link smf_db_create_table()})
Add After: Select
* Note that $column_info also supports two additional parameters that only make sense when changing columns:
* - drop_default - to drop a default that was previously specified
Find: Select
if (isset($column_info['null']))
$column_info['not_null'] = !$column_info['null'];
if (isset($old_info['null']))
$old_info['not_null'] = !$old_info['null'];

Replace With: Select
if (isset($column_info['null']) && !isset($column_info['not_null']))
$column_info['not_null'] = !$column_info['null'];

Find: Select
if (!isset($column_info['name']))
$column_info['name'] = $old_column;
if (!isset($column_info['default']))
$column_info['default'] = $old_info['default'];
Replace With: Select
if (isset($column_info['drop_default']) && !empty($column_info['drop_default']))
$column_info['drop_default'] = true;
else
$column_info['drop_default'] = false;
if (!isset($column_info['name']))
$column_info['name'] = $old_column;
if (!array_key_exists('default', $column_info) && array_key_exists('default', $old_info) && empty($column_info['drop_default']))
$column_info['default'] = $old_info['default'];
Find: Select
list ($type, $size) = $smcFunc['db_calculate_type']($column_info['type'], $column_info['size']);
Add Before: Select
// If truly unspecified, make that clear, otherwise, might be confused with NULL...
// (Unspecified = no default whatsoever = column is not nullable with a value of null...)
if (($column_info['not_null'] === true) && !$column_info['drop_default'] && array_key_exists('default', $column_info) && is_null($column_info['default']))
unset($column_info['default']);

Find: Select
// Fix the default.
$default = '';
if (array_key_exists('default', $column_info) && is_null($column_info['default']))
$default = 'NULL';
elseif (isset($column_info['default']) && is_numeric($column_info['default']))
$default = strpos($column_info['default'], '.') ? floatval($column_info['default']) : intval($column_info['default']);
else
$default = '\'' . $smcFunc['db_escape_string']($column_info['default']) . '\'';
Replace With: Select
// If you need to drop the default, that needs it's own thing...
// Must be done first, in case the default type is inconsistent with the other changes.
if ($column_info['drop_default'])
{
$smcFunc['db_query']('', '
ALTER TABLE ' . $short_table_name . '
ALTER COLUMN `' . $old_column . '` DROP DEFAULT',
array(
'security_override' => true,
)
);
}

// Set the default clause.
$default_clause = '';
if (!$column_info['drop_default'] && array_key_exists('default', $column_info))
{
if (is_null($column_info['default']))
$default_clause = 'DEFAULT NULL';
elseif (is_numeric($column_info['default']))
$default_clause = 'DEFAULT ' . (strpos($column_info['default'], '.') ? floatval($column_info['default']) : intval($column_info['default']));
elseif (is_string($column_info['default']))
$default_clause = 'DEFAULT \'' . $smcFunc['db_escape_string']($column_info['default']) . '\'';
}
Find: Select
($default === '' ? '' : 'DEFAULT ' . $default) . ' ' .
Replace With: Select
$default_clause . ' ' .

./Sources/DbPackages-postgresql.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
* Change a column.
Replace With: Select
* Change a column. You only need to specify the column attributes that are changing.
Find: Select
* @param array $column_info An array of info about the "new" column definition (see {@link smf_db_create_table()})
Add After: Select
* Note that $column_info also supports two additional parameters that only make sense when changing columns:
* - drop_default - to drop a default that was previously specified
Find: Select
// backward compatibility
if (isset($column_info['null']) && !isset($column_info['not_null']))
$column_info['not_null'] = !$column_info['null'];

// Check it does exist!
Replace With: Select
// Check it does exist!
Find: Select
// Now we check each bit individually and ALTER as required.
if (isset($column_info['name']) && $column_info['name'] != $old_column)
{
$smcFunc['db_query']('', '
ALTER TABLE ' . $short_table_name . '
RENAME COLUMN ' . $old_column . ' TO ' . $column_info['name'],
array(
'security_override' => true,
)
);
}
// Different default?
if (array_key_exists('default', $column_info) && $column_info['default'] != $old_info['default'])
{
// Fix the default.
$default = '';
if (array_key_exists('default', $column_info) && is_null($column_info['default']))
$default = 'NULL';
elseif (isset($column_info['default']) && is_numeric($column_info['default']))
$default = strpos($column_info['default'], '.') ? floatval($column_info['default']) : intval($column_info['default']);
else
$default = '\'' . $smcFunc['db_escape_string']($column_info['default']) . '\'';
Replace With: Select
// backward compatibility
if (isset($column_info['null']) && !isset($column_info['not_null']))
$column_info['not_null'] = !$column_info['null'];
Find: Select
$action = $default === '' ? 'DROP DEFAULT' : 'SET DEFAULT ' . $default;
Replace With: Select
// Get the right bits.
if (isset($column_info['drop_default']) && !empty($column_info['drop_default']))
$column_info['drop_default'] = true;
else
$column_info['drop_default'] = false;
if (!isset($column_info['name']))
$column_info['name'] = $old_column;
if (!array_key_exists('default', $column_info) && array_key_exists('default', $old_info) && empty($column_info['drop_default']))
$column_info['default'] = $old_info['default'];
if (!isset($column_info['not_null']))
$column_info['not_null'] = $old_info['not_null'];
if (!isset($column_info['auto']))
$column_info['auto'] = $old_info['auto'];
if (!isset($column_info['type']))
$column_info['type'] = $old_info['type'];
if (!isset($column_info['size']) || !is_numeric($column_info['size']))
$column_info['size'] = $old_info['size'];
if (!isset($column_info['unsigned']) || !in_array($column_info['type'], array('int', 'tinyint', 'smallint', 'mediumint', 'bigint')))
$column_info['unsigned'] = '';

// If truly unspecified, make that clear, otherwise, might be confused with NULL...
// (Unspecified = no default whatsoever = column is not nullable with a value of null...)
if (($column_info['not_null'] === true) && !$column_info['drop_default'] && array_key_exists('default', $column_info) && is_null($column_info['default']))
unset($column_info['default']);

// If you need to drop the default, that needs it's own thing...
// Must be done first, in case the default type is inconsistent with the other changes.
if ($column_info['drop_default'])
{
Find: Select
ALTER COLUMN ' . $column_info['name'] . ' ' . $action,
Replace With: Select
ALTER COLUMN ' . $old_column . ' DROP DEFAULT',
Find: Select
// Is it null - or otherwise?
if (isset($column_info['not_null']) && $column_info['not_null'] != $old_info['not_null'])
{
$action = $column_info['not_null'] ? 'SET' : 'DROP';
$smcFunc['db_transaction']('begin');
if ($column_info['not_null'])
{
// We have to set it to something if we are making it NOT NULL. And we must comply with the current column format.
$setTo = isset($column_info['default']) ? $column_info['default'] : (strpos($old_info['type'], 'int') !== false ? 0 : '');
$smcFunc['db_query']('', '
UPDATE ' . $short_table_name . '
SET ' . $column_info['name'] . ' = \'' . $setTo . '\'
WHERE ' . $column_info['name'] . ' IS NULL',
array(
'security_override' => true,
)
);
}
$smcFunc['db_query']('', '
Replace With: Select
// Now we check each bit individually and ALTER as required.
if (isset($column_info['name']) && $column_info['name'] != $old_column)
{
$smcFunc['db_query']('', '
Find: Select
ALTER COLUMN ' . $column_info['name'] . ' ' . $action . ' NOT NULL',
array(
'security_override' => true,
)
);
$smcFunc['db_transaction']('commit');
}
// What about a change in type?
Replace With: Select
RENAME COLUMN ' . $old_column . ' TO ' . $column_info['name'],
array(
'security_override' => true,
)
);
}

// What about a change in type?
Find: Select
// Finally - auto increment?!
if (isset($column_info['auto']) && $column_info['auto'] != $old_info['auto'])
Replace With: Select

// Different default?
// Just go ahead & honor the setting. Type changes above introduce defaults that we might need to override here...
if (!$column_info['drop_default'] && array_key_exists('default', $column_info))
Find: Select
// Are we removing an old one?
if ($old_info['auto'])
{
// Alter the table first - then drop the sequence.
$smcFunc['db_query']('', '
ALTER TABLE ' . $short_table_name . '
ALTER COLUMN ' . $column_info['name'] . ' SET DEFAULT \'0\'',
array(
'security_override' => true,
)
);
$smcFunc['db_query']('', '
DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq',
array(
'security_override' => true,
)
);
}
// Otherwise add it!
else
{
$smcFunc['db_query']('', '
DROP SEQUENCE IF EXISTS ' . $short_table_name . '_seq',
array(
'security_override' => true,
)
);

$smcFunc['db_query']('', '
CREATE SEQUENCE ' . $short_table_name . '_seq',
array(
'security_override' => true,
)
);
$smcFunc['db_query']('', '
ALTER TABLE ' . $short_table_name . '
ALTER COLUMN ' . $column_info['name'] . ' SET DEFAULT nextval(\'' . $short_table_name . '_seq\')',
array(
'security_override' => true,
)
);
}
}

return true;
Replace With: Select
// Fix the default.
$default = '';
if (is_null($column_info['default']))
$default = 'NULL';
elseif (isset($column_info['default']) && is_numeric($column_info['default']))
$default = strpos($column_info['default'], '.') ? floatval($column_info['default']) : intval($column_info['default']);
else
$default = '\'' . $smcFunc['db_escape_string']($column_info['default']) . '\'';

$action = 'SET DEFAULT ' . $default;
$smcFunc['db_query']('', '
ALTER TABLE ' . $short_table_name . '
ALTER COLUMN ' . $column_info['name'] . ' ' . $action,
array(
'security_override' => true,
)
);
}

// Is it null - or otherwise?
// Just go ahead & honor the setting. Type changes above introduce defaults that we might need to override here...
if ($column_info['not_null'])
$action = 'SET NOT NULL';
else
$action = 'DROP NOT NULL';

$smcFunc['db_query']('', '
ALTER TABLE ' . $short_table_name . '
ALTER COLUMN ' . $column_info['name'] . ' ' . $action,
array(
'security_override' => true,
)
);

return true;
Find: Select
// What is the default?
if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0)
{
$default = null;
$auto = true;
}
Replace With: Select
$default = null;
// What is the default?
if ($row['column_default'] !== null)
{
if (preg_match('~nextval\(\'(.+?)\'(.+?)*\)~i', $row['column_default'], $matches) != 0)
$auto = true;
elseif (substr($row['column_default'], 0, 4) != 'NULL' && trim($row['column_default']) != '')
{
$pos = strpos($row['column_default'], '::');
$default = trim($pos === false ? $row['column_default'] : substr($row['column_default'], 0, $pos), '\'');
}
}
Find: Select
elseif (trim($row['column_default']) != '')
$default = trim(strpos($row['column_default'], '::') === false ? $row['column_default'] : substr($row['column_default'], 0, strpos($row['column_default'], '::')), '\'');
else
$default = null;

// Make the type generic.
Replace With: Select

// Make the type generic.
Find: Select
'not_null' => $row['is_nullable'] == 'YES' ? false : true,
'null' => $row['is_nullable'] == 'YES' ? true : false,
Replace With: Select
'not_null' => $row['is_nullable'] != 'YES',
'null' => $row['is_nullable'] == 'YES',

./Sources/DbSearch-mysql.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
);
Add After: Select

db_extend();
$version = $smcFunc['db_get_version']();
$smcFunc['db_supports_pcre'] = version_compare($version, strpos($version, 'MariaDB') !== false ? '10.0.5' : '8.0.4', '>=');

./Sources/DbSearch-postgresql.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
$smcFunc['db_support_ignore'] = true;
Add After: Select
$smcFunc['db_supports_pcre'] = true;
Find: Select
'insert_into_log_messages_fulltext' => array(
'~LIKE~i' => 'iLIKE',
'~NOT\sLIKE~i' => '~NOT iLIKE',
'~NOT\sRLIKE~i' => '!~*',
'~RLIKE~i' => '~*',
Replace With: Select
'insert_into_log_messages_fulltext' => array(
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',
Find: Select
'~LIKE~i' => 'iLIKE',
'~NOT\sLIKE~i' => 'NOT iLIKE',
'~NOT\sRLIKE~i' => '!~*',
'~RLIKE~i' => '~*',
Replace With: Select
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',
),
'insert_log_search_topics' => array(
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',
),
'insert_log_search_results_no_index' => array(
'/NOT\sLIKE/' => 'NOT ILIKE',
'/\bLIKE\b/' => 'ILIKE',
'/NOT RLIKE/' => '!~*',
'/RLIKE/' => '~*',

./Sources/Display.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
loadTemplate('Display');
Add After: Select
loadCSSFile('attachments.css', array('minimize' => true, 'order_pos' => 450), 'smf_attachments');

./Sources/Load.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
'smf_quote_expand' => !empty($modSettings['quote_expand']) ? $modSettings['quote_expand'] : 'false',
Add Before: Select
'smf_collapseAlt' => JavaScriptEscape($txt['hide']),
'smf_expandAlt' => JavaScriptEscape($txt['show']),
Find: Select
// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
if ((!empty($modSettings['mail_next_send']) && $modSettings['mail_next_send'] < time() && empty($modSettings['mail_queue_use_cron'])) || empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
{
if (isBrowser('possibly_robot'))
{
// @todo Maybe move this somewhere better?!
require_once($sourcedir . '/ScheduledTasks.php');

// What to do, what to do?!
if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
AutoTask();
else
ReduceMailQueue();
}
else
{
$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];

addInlineJavaScript('
function smfAutoTask()
{
$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
}
window.setTimeout("smfAutoTask();", 1);');
}
}

// And we should probably trigger the cron too.
Replace With: Select
// And we should probably trigger the cron too.
Find: Select
window.setTimeout(triggerCron, 1);', true);
Add After: Select

// Robots won't normally trigger cron.php, so for them run the scheduled tasks directly.
if (isBrowser('possibly_robot') && (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() || (!empty($modSettings['mail_next_send']) && $modSettings['mail_next_send'] < time() && empty($modSettings['mail_queue_use_cron']))))
{
require_once($sourcedir . '/ScheduledTasks.php');

// What to do, what to do?!
if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
AutoTask();
else
ReduceMailQueue();
}

./Sources/Logging.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
$encoded_get = truncate_array($_GET) + array('USER_AGENT' => $_SERVER['HTTP_USER_AGENT']);
Replace With: Select
$encoded_get = truncate_array($_GET) + array('USER_AGENT' => mb_substr($_SERVER['HTTP_USER_AGENT'], 0, 128));
Find: Select
$encoded_get = $smcFunc['json_encode']($encoded_get);
Add After: Select

// Sometimes folks mess with USER_AGENT & $_GET data, so one last check to avoid 'data too long' errors
if (mb_strlen($encoded_get) > 2048)
$encoded_get = '';

./Sources/ManageBans.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
'href' => $scripturl . '?action=admin;area=ban;sa=list',
'is_selected' => $_REQUEST['sa'] == 'list' || $_REQUEST['sa'] == 'edit' || $_REQUEST['sa'] == 'edittrigger',
Replace With: Select
'href' => $scripturl . '?action=admin;area=ban;sa=list',
Find: Select
'href' => $scripturl . '?action=admin;area=ban;sa=add',
'is_selected' => $_REQUEST['sa'] == 'add',
Replace With: Select
'href' => $scripturl . '?action=admin;area=ban;sa=add',
Find: Select
'href' => $scripturl . '?action=admin;area=ban;sa=browse',
'is_selected' => $_REQUEST['sa'] == 'browse',
Replace With: Select
'href' => $scripturl . '?action=admin;area=ban;sa=browse',
Find: Select
'href' => $scripturl . '?action=admin;area=ban;sa=log',
'is_selected' => $_REQUEST['sa'] == 'log',
Replace With: Select
'href' => $scripturl . '?action=admin;area=ban;sa=log',
Find: Select
$context['page_title'] = $txt['ban_title'];
Add Before: Select
// Mark the appropriate menu entry as selected
if (array_key_exists($_REQUEST['sa'], $context[$context['admin_menu_name']]['tab_data']['tabs']))
$context[$context['admin_menu_name']]['tab_data']['tabs'][$_REQUEST['sa']]['is_selected'] = true;

./Sources/ManageMaintenance.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
$function_remove = urldecode($_REQUEST['function']) . (($_REQUEST['do'] == 'disable') ? '' : '!');
$function_add = urldecode($_REQUEST['function']) . (($_REQUEST['do'] == 'disable') ? '!' : '');
Replace With: Select
// Disable/enable logic; always remove exactly what was passed
$function_remove = urldecode($_REQUEST['function']);
$function_add = urldecode(rtrim($_REQUEST['function'], '!')) . (($_REQUEST['do'] == 'disable') ? '!' : '');
Find: Select
$change_status = array('before' => '', 'after' => '');

if ($data['can_disable'])
Replace With: Select
// Cannot update temp hooks in any way, really. Just show the appropriate icon.
if ($data['status'] == 'temp')
Find: Select
$change_status['before'] = '<a href="' . $scripturl . '?action=admin;area=maintain;sa=hooks;do=' . ($data['enabled'] ? 'disable' : 'enable') . ';hook=' . $data['hook_name'] . ';function=' . urlencode($data['real_function']) . $filter_url . ';' . $context['admin-hook_token_var'] . '=' . $context['admin-hook_token'] . ';' . $context['session_var'] . '=' . $context['session_id'] . '" data-confirm="' . $txt['quickmod_confirm'] . '" class="you_sure">';
$change_status['after'] = '</a>';
Replace With: Select
return '<span class="main_icons ' . ($data['hook_exists'] ? 'posts' : 'error') . '" title="' . $data['img_text'] . '"></span>';
Find: Select
}
Add After: Select
else
{
$change_status = array('before' => '', 'after' => '');
Find: Select
return $change_status['before'] . '<span class="main_icons post_moderation_' . $data['status'] . '" title="' . $data['img_text'] . '"></span>' . $change_status['after'];
Replace With: Select
// Can only enable/disable if it exists...
if ($data['hook_exists'])
{
$change_status['before'] = '<a href="' . $scripturl . '?action=admin;area=maintain;sa=hooks;do=' . ($data['enabled'] ? 'disable' : 'enable') . ';hook=' . $data['hook_name'] . ';function=' . urlencode($data['function_name']) . $filter_url . ';' . $context['admin-hook_token_var'] . '=' . $context['admin-hook_token'] . ';' . $context['session_var'] . '=' . $context['session_id'] . '" data-confirm="' . $txt['quickmod_confirm'] . '" class="you_sure">';
$change_status['after'] = '</a>';
}

return $change_status['before'] . '<span class="main_icons post_moderation_' . $data['status'] . '" title="' . $data['img_text'] . '"></span>' . $change_status['after'];
}
Find: Select
<li><span class="main_icons post_moderation_deny"></span> ' . $txt['hooks_disable_legend_missing'] . '</li>
Add After: Select
<li><span class="main_icons posts"></span> ' . $txt['hooks_disable_legend_temp'] . '</li>
<li><span class="main_icons error"></span> ' . $txt['hooks_disable_legend_temp_missing'] . '</li>
Find: Select
if (!$data['hook_exists'])
Replace With: Select
// Note: Cannot remove temp hooks via the UI...
if (!$data['hook_exists'] && $data['status'] != 'temp')
Find: Select
if ($hookParsedData['absPath'] != '' && !isset($files[$hookParsedData['absPath']]) && file_exists($hookParsedData['absPath']))
$function_list += get_defined_functions_in_file($hookParsedData['absPath']);
Replace With: Select
$absPath_clean = rtrim($hookParsedData['absPath'], '!');
if ($absPath_clean != '' && !isset($files[$absPath_clean]) && file_exists($absPath_clean))
$function_list += get_defined_functions_in_file($absPath_clean);
Find: Select
$hook_exists = isset($function_list[$hookParsedData['call']]) || (substr($hook, -8) === '_include' && isset($files[$hookParsedData['absPath']]));
Replace With: Select
$hook_exists = isset($function_list[$hookParsedData['call']]) || (substr($hook, -8) === '_include' && isset($files[$absPath_clean]));
$hook_temp = !empty($context['integration_hooks_temporary'][$hook][$hookParsedData['rawData']]);
Find: Select
'status' => $hook_exists ? ($hookParsedData['enabled'] ? 'allow' : 'moderate') : 'deny',
'img_text' => $txt['hooks_' . ($hook_exists ? ($hookParsedData['enabled'] ? 'active' : 'disabled') : 'missing')],
Replace With: Select
'status' => ($hook_temp ? 'temp' : ($hook_exists ? ($hookParsedData['enabled'] ? 'allow' : 'moderate') : 'deny')),
'img_text' => $txt['hooks_' . ($hook_exists ? ($hook_temp ? 'temp' : ($hookParsedData['enabled'] ? 'active' : 'disabled')) : 'missing')],
Find: Select
'enabled' => $hookParsedData['enabled'],
'can_disable' => $hookParsedData['call'] != '',
Replace With: Select
'enabled' => $hookParsedData['enabled'],
Find: Select
if (strpos($modFunc, '!') !== false)
Replace With: Select
// May need to inspect $rawData here for includes...
if ((strpos($modFunc, '!') !== false) || (empty($modFunc) && (strpos($rawData, '!') !== false)))

./Sources/ManagePaid.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
// Is this a fixed one?
Add Before: Select
// Cleanup text fields
$_POST['name'] = $smcFunc['htmlspecialchars']($_POST['name']);
$_POST['desc'] = $smcFunc['htmlspecialchars']($_POST['desc']);
$emailComplete = $smcFunc['htmlspecialchars']($emailComplete);

Find: Select
'email_complete' => $smcFunc['htmlspecialchars']($row['email_complete']),
Replace With: Select
'email_complete' => $row['email_complete'],

./Sources/Memberlist.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
This operation isn't vital to the installation of this mod.
Find: Select
* @version 2.1.2
Replace With: Select
* @version 2.1.4
Find: Select
$_POST['search'] = trim(isset($_GET['search']) ? $_GET['search'] : $_POST['search']);
Replace With: Select
$_POST['search'] = trim(isset($_GET['search']) ? html_entity_decode(htmlspecialchars_decode($_GET['search'], ENT_QUOTES), ENT_QUOTES) : $_POST['search']);
Find: Select
$context['old_search'] = $_REQUEST['search'];
$context['old_search_value'] = urlencode($_REQUEST['search']);
Replace With: Select
$_POST['search'] = $_REQUEST['search'] = $smcFunc['htmlspecialchars']($_POST['search'], ENT_QUOTES);

$context['old_search'] = $_POST['search'];
$context['old_search_value'] = urlencode($_POST['search']);
Find: Select
// Set defaults for how the results are sorted
Add Before: Select
$_POST['fields'] = array_intersect($_POST['fields'], array_merge(array('name', 'website', 'group', 'email'), array_keys($context['custom_search_fields'])));

Find: Select
$context['columns'][$col]['href'] .= ';search=' . $_POST['search'] . ';fields=' . implode(',', $_POST['fields']);
Replace With: Select
$context['columns'][$col]['href'] .= ';search=' . urlencode($_POST['search']) . ';fields=' . implode(',', $_POST['fields']);
Find: Select
'search' => '%' . strtr($smcFunc['htmlspecialchars']($_POST['search'], ENT_QUOTES), array('_' => '\\_', '%' => '\\%', '*' => '%')) . '%',
Replace With: Select
'search' => '%' . strtr($_POST['search'], array('_' => '\\_', '%' => '\\%', '*' => '%')) . '%',
Find: Select
$row['col_name'] = substr($field, 5);
if (substr($field, 0, 5) == 'cust_' && isset($context['custom_search_fields'][$row['col_name']]))
Replace With: Select
if (substr($field, 0, 5) == 'cust_' && isset($context['custom_search_fields'][$field]))
Find: Select
$customJoin[] = 'LEFT JOIN {db_prefix}themes AS t' . $row['col_name'] . ' ON (t' . $row['col_name'] . '.variable = {string:t' . $row['col_name'] . '} AND t' . $row['col_name'] . '.id_theme = 1 AND t' . $row['col_name'] . '.id_member = mem.id_member)';
$query_parameters['t' . $row['col_name']] = $row['col_name'];
$fields += array($customCount++ => 'COALESCE(t' . $row['col_name'] . '.value, {string:blank_string})');
Replace With: Select
$customJoin[] = 'LEFT JOIN {db_prefix}themes AS t' . $field . ' ON (t' . $field . '.variable = {string:t' . $field . '} AND t' . $field . '.id_theme = 1 AND t' . $field . '.id_member = mem.id_member)';
$query_parameters['t' . $field] = $field;
$fields += array($customCount++ => 'COALESCE(t' . $field . '.value, {string:blank_string})');
Find: Select
LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_id_group} THEN mem.id_post_group ELSE mem.id_group END)' .
(empty($customJoin) ? '' : implode('
Replace With: Select
LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = CASE WHEN mem.id_group = {int:regular_id_group} THEN mem.id_post_group ELSE mem.id_group END)
' . (empty($customJoin) ? '' : implode('
Find: Select
$context['page_index'] = constructPageIndex($scripturl . '?action=mlist;sa=search;search=' . $_POST['search'] . ';fields=' . implode(',', $_POST['fields']), $_REQUEST['start'], $numResults, $modSettings['defaultMaxMembers']);
Replace With: Select
$context['page_index'] = constructPageIndex($scripturl . '?action=mlist;sa=search;search=' . urlencode($_POST['search']) . ';fields=' . implode(',', $_POST['fields']), $_REQUEST['start'], $numResults, $modSettings['defaultMaxMembers']);
Find: Select
if (array_search('cust_' . $_REQUEST['sort'], $_POST['fields']) === false && !empty($context['custom_profile_fields']['join'][$_REQUEST['sort']]))
Replace With: Select
if (array_search($_REQUEST['sort'], $_POST['fields']) === false && !empty($context['custom_profile_fields']['join'][$_REQUEST['sort']]))
Find: Select
$context['search_fields']['cust_' . $field['colname']] = sprintf($txt['mlist_search_by'], tokenTxtReplace($field['name']));
Replace With: Select
$context['search_fields'][$field['colname']] = sprintf($txt['mlist_search_by'], tokenTxtReplace($field['name']));

./Sources/PersonalMessage.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' .
($context['sort_by'] == 'name' ? ', mem.real_name' : '')
Replace With: Select
GROUP BY pm.id_pm, pm.subject, pm.id_member_from, pm.body, pm.msgtime, pm.from_name' .
($context['sort_by'] == 'name' ? ', mem.real_name' : '')
Find: Select
WHERE pm.id_pm IN ({array_int:display_pms})' .
Replace With: Select
WHERE pm.id_pm IN ({array_int:display_pms})' .
Find: Select
// Add all existing labels to the array to save, slashing them as necessary...
Replace With: Select
// Add all of the current user's existing labels to the array to save, slashing them as necessary...
Find: Select
unset($the_labels[$label]);
$labels_to_remove[] = $label;
Replace With: Select
if (array_key_exists($label, $the_labels))
{
unset($the_labels[$label]);
$labels_to_remove[] = $label;
}
Find: Select
WHERE id_label = {int:id_label}',
Replace With: Select
WHERE id_label = {int:id_label}
AND id_member = {int:current_member}',
Find: Select
'id_label' => $id
Replace With: Select
'id_label' => $id,
'current_member' => $user_info['id'],
Find: Select
DELETE FROM {db_prefix}pm_labels
WHERE id_label IN ({array_int:labels_to_delete})',
Replace With: Select
DELETE FROM {db_prefix}pm_labels
WHERE id_label IN ({array_int:labels_to_delete})
AND id_member = {int:current_member}',
Find: Select
)
);

// Now remove the now-deleted labels from any PMs...
Add Before: Select
'current_member' => $user_info['id'],
Find: Select
'in_inbox' => 1,
Add After: Select
'current_member' => $user_info['id'],

./Sources/Post.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
$context['num_allowed_attachments'] = !isset($modSettings['attachmentNumPerPostLimit']) ? 4 : $modSettings['attachmentNumPerPostLimit'];
Replace With: Select
$context['num_allowed_attachments'] = empty($modSettings['attachmentNumPerPostLimit']) ? PHP_INT_MAX : $modSettings['attachmentNumPerPostLimit'];
Find: Select
$context['attachment_restrictions'][$type] .= '<span class="attach_available"> (' . sprintf($txt['attach_available'], max($modSettings['attachmentPostLimit'] - ($context['attachments']['total_size'] / 1024), 0)) . ')</span>';
Replace With: Select
$context['attachment_restrictions'][$type] .= '<span class="attach_available"> (' . sprintf($txt['attach_available'], round(max($modSettings['attachmentPostLimit'] - ($context['attachments']['total_size'] / 1024), 0), 2)) . ')</span>';
Find: Select
text_attachLeft: ' . JavaScriptEscape($txt['attachments_left']) . ',
Add Before: Select
text_attachDropzoneLabel: ' . JavaScriptEscape($txt['attach_drop_zone']) . ',
text_attachLimitNag: ' . JavaScriptEscape($txt['attach_limit_nag']) . ',
Find: Select
text_max_size_progress: ' . JavaScriptEscape($txt['attach_max_size_progress']) . ',
Replace With: Select
text_max_size_progress: ' . JavaScriptEscape('{currentRemain} ' . ($modSettings[$type] >= 1024 ? $txt['megabyte'] : $txt['kilobyte']) . ' / {currentTotal} ' . ($modSettings[$type] >= 1024 ? $txt['megabyte'] : $txt['kilobyte'])) . ',
Find: Select
// Finally, load the template.
Add Before: Select
// If we're editing and displaying edit details, show a box where they can say why.
if (isset($context['editing']) && $modSettings['show_modify'])
{
$context['posting_fields']['modify_reason'] = array(
'label' => array(
'text' => $txt['reason_for_edit'],
),
'input' => array(
'type' => 'text',
'attributes' => array(
'size' => 80,
'maxlength' => 80,
'value' => isset($context['last_modified_reason']) ? $context['last_modified_reason'] : '',
),
),
);

// If this message has been edited in the past - display when it was.
if (!empty($context['last_modified_text']))
{
$context['posting_fields']['modified_time'] = array(
'label' => array(
'text' => $txt['modified_time'],
),
'input' => array(
'type' => '',
'html' => !empty($context['last_modified_text']) ? ltrim(preg_replace('~<span[^>]*>[^<]*</span>~u', '', $context['last_modified_text']), ': ') : '',
),
);
}

// Prior to 2.1.4, the edit reason was not handled as a posting field,
// but instead using a hardcoded input in the template file. We've fixed
// that in the default theme, but to support any custom themes based on
// the old verison, we do this to fix it for them.
addInlineCss("\n\t" . '#caption_edit_reason, dl:not(#post_header) input[name="modify_reason"] { display: none; }');
addInlineJavaScript("\n\t" . '$("#caption_edit_reason").remove(); $("dl:not(#post_header) input[name=\"modify_reason\"]").remove();', true);
}

Find: Select
// Finally, load the template.
if (!isset($_REQUEST['xml']))
Add After: Select
{
Find: Select
call_integration_hook('integrate_post_end');
Add Before: Select
// These two lines are for the revamped attachments UI add in 2.1.4.
loadCSSFile('attachments.css', array('minimize' => true, 'order_pos' => 450), 'smf_attachments');
addInlineJavaScript("\n\t" . '$("#post_attachments_area #postAttachment").remove();', true);
}

./Sources/Profile.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
// Let them modify profile areas easily.
Add After: Select
call_integration_hook('integrate_profile_areas', array(&$profile_areas));

// Deprecated since 2.1.4 and will be removed in 3.0.0. Kept for compatibility with early versions of 2.1.
// @todo add runtime warnings.
Find: Select
'disable_url_session_check' => true,
Add Before: Select
'disable_hook_call' => true,

./Sources/RemoveTopic.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
GROUP BY id_topic, approved
Replace With: Select
GROUP BY id_topic, approved, subject

./Sources/Search.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
This operation isn't vital to the installation of this mod.
Find: Select
* @version 2.1.2
Replace With: Select
* @version 2.1.4
Find: Select
$no_regexp = preg_match('~&#(?:\d{1,7}|x[0-9a-fA-F]{1,6});~', $stripped_query) === 1;
Add After: Select
$is_search_regex = !empty($modSettings['search_match_words']) && !$no_regexp;

// Specify the function to search with. Regex is for word boundaries.
$query_match_type = $is_search_regex ? 'RLIKE' : 'LIKE';
$word_boundary_wrapper = function(string $str) use ($smcFunc): string
{
return sprintf($smcFunc['db_supports_pcre'] ? '\\b%s\\b' : '[[:<:]]%s[[:>:]]', $str);
};
$escape_sql_regex = function(string $str): string
{
return addcslashes(preg_replace('/[\[\]$.+*?&^|{}()]/', '[$0]', $str), '\\\'');
};
Find: Select
$wordArray = preg_replace('~(?:^|\s)(?:[-]?)"(?:[^"]+)"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']);
Replace With: Select
$wordArray = preg_replace('~(?:^|\s)[-]?"[^"]+"(?:$|\s)~' . ($context['utf8'] ? 'u' : ''), ' ', $search_params['search']);
Find: Select
{
$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
Replace With: Select
{
$subject_query['inner_join']['m'] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
Find: Select
$subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:excluded_phrases_' . $count . '}';
Replace With: Select
$subject_query['where'][] = 'm.subject NOT ' . $query_match_type . ' {string:excluded_phrases_' . $count . '}';
Find: Select
$subject_query_params['excluded_phrases_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
Replace With: Select
if ($is_search_regex)
$subject_query_params['excluded_phrases_' . $count++] = $word_boundary_wrapper($escape_sql_regex($phrase));
else
$subject_query_params['excluded_phrases_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($phrase) . '%';
Find: Select
$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
Replace With: Select
$subject_query['inner_join']['m'] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
Find: Select
$subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:body_not_' . $count . '}';
$subject_query['params']['body_not_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($subjectWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $subjectWord), '\\\'') . '[[:>:]]';
Replace With: Select
$subject_query['where'][] = 'm.body NOT ' . $query_match_type . ' {string:body_not_' . $count . '}';

if ($is_search_regex)
$subject_query['params']['body_not_' . $count++] = $word_boundary_wrapper($escape_sql_regex($subjectWord));
else
$subject_query['params']['body_not_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($subjectWord) . '%';
Find: Select
$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
}
$subject_query['where'][] = '{raw:user_query}';
Replace With: Select
$subject_query['inner_join']['m'] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
}
$subject_query['where'][] = '{raw:user_query}';
Find: Select
$subject_query['inner_join'][] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
}
$count = 0;
Replace With: Select
$subject_query['inner_join']['m'] = '{db_prefix}messages AS m ON (m.id_msg = t.id_first_msg)';
}
$count = 0;
Find: Select
$subject_query['where'][] = 'm.subject NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}';
$subject_query['where'][] = 'm.body NOT ' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:exclude_phrase_' . $count . '}';
$subject_query['params']['exclude_phrase_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
Replace With: Select
$subject_query['where'][] = 'm.subject NOT ' . $query_match_type . ' {string:exclude_phrase_' . $count . '}';
$subject_query['where'][] = 'm.body NOT ' . $query_match_type . ' {string:exclude_phrase_' . $count . '}';

if ($is_search_regex)
$subject_query['params']['exclude_phrase_' . $count++] = $word_boundary_wrapper($escape_sql_regex($phrase));
else
$subject_query['params']['exclude_phrase_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($phrase) . '%';
Find: Select
$where[] = 'm.body' . (in_array($regularWord, $excludedWords) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}';
if (in_array($regularWord, $excludedWords))
$where[] = 'm.subject NOT' . (empty($modSettings['search_match_words']) || $no_regexp ? ' LIKE ' : ' RLIKE ') . '{string:all_word_body_' . $count . '}';
$main_query['parameters']['all_word_body_' . $count++] = empty($modSettings['search_match_words']) || $no_regexp ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]';
}
Replace With: Select
if (in_array($regularWord, $excludedWords))
{
$where[] = 'm.subject NOT ' . $query_match_type . ' {string:all_word_body_' . $count . '}';
$where[] = 'm.body NOT ' . $query_match_type . ' {string:all_word_body_' . $count . '}';
}
else
$where[] = 'm.body ' . $query_match_type . ' {string:all_word_body_' . $count . '}';

if ($is_search_regex)
$main_query['parameters']['all_word_body_' . $count++] = $word_boundary_wrapper($escape_sql_regex($regularWord));
else
$main_query['parameters']['all_word_body_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($regularWord) . '%';
}

./Sources/SearchAPI-Custom.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
$query_select = array(
Add Before: Select
// Specify the function to search with. Regex is for word boundaries.
$is_search_regex = !empty($modSettings['search_match_words']) && !$search_data['no_regexp'];
$query_match_type = $is_search_regex ? 'RLIKE' : 'LIKE';
$word_boundary_wrapper = function(string $str) use ($smcFunc): string
{
return sprintf($smcFunc['db_supports_pcre'] ? '\\b%s\\b' : '[[:<:]]%s[[:>:]]', $str);
};
$escape_sql_regex = function(string $str): string
{
return addcslashes(preg_replace('/[\[\]$.+*?&^|{}()]/', '[$0]', $str), '\\\'');
};

Find: Select
$query_where[] = 'm.body' . (in_array($regularWord, $query_params['excluded_words']) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:complex_body_' . $count . '}';
$query_params['complex_body_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]';
Replace With: Select
if (in_array($regularWord, $query_params['excluded_words']))
$query_where[] = 'm.body NOT ' . $query_match_type . ' {string:complex_body_' . $count . '}';
else
$query_where[] = 'm.body ' . $query_match_type . ' {string:complex_body_' . $count . '}';

if ($is_search_regex)
$query_params['complex_body_' . $count++] = $word_boundary_wrapper($escape_sql_regex($regularWord));
else
$query_params['complex_body_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($regularWord) . '%';
Find: Select
foreach ($query_params['excluded_phrases'] as $phrase)
{
$query_where[] = 'subject NOT ' . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:exclude_subject_phrase_' . $count . '}';
$query_params['exclude_subject_phrase_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
Replace With: Select
foreach ($query_params['excluded_phrases'] as $phrase)
{
$query_where[] = 'subject NOT ' . $query_match_type . ' {string:exclude_subject_words_' . $count . '}';

if ($is_search_regex)
$query_params['exclude_subject_words_' . $count++] = $word_boundary_wrapper($escape_sql_regex($excludedWord));
else
$query_params['exclude_subject_words_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($excludedWord) . '%';
Find: Select
foreach ($query_params['excluded_subject_words'] as $excludedWord)
{
$query_where[] = 'subject NOT ' . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:exclude_subject_words_' . $count . '}';
$query_params['exclude_subject_words_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($excludedWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $excludedWord), '\\\'') . '[[:>:]]';
Replace With: Select
foreach ($query_params['excluded_subject_words'] as $excludedWord)
{
$query_where[] = 'subject NOT ' . $query_match_type . ' {string:exclude_subject_words_' . $count . '}';

if ($is_search_regex)
$query_params['exclude_subject_words_' . $count++] = $word_boundary_wrapper($escape_sql_regex($excludedWord));
else
$query_params['exclude_subject_words_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($excludedWord) . '%';

./Sources/SearchAPI-Fulltext.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
$query_select = array(
Add Before: Select
// Specify the function to search with. Regex is for word boundaries.
$is_search_regex = !empty($modSettings['search_match_words']) && !$search_data['no_regexp'];
$query_match_type = $is_search_regex ? 'RLIKE' : 'LIKE';
$word_boundary_wrapper = function(string $str) use ($smcFunc): string
{
return sprintf($smcFunc['db_supports_pcre'] ? '\\b%s\\b' : '[[:<:]]%s[[:>:]]', $str);
};
$escape_sql_regex = function(string $str): string
{
return addcslashes(preg_replace('/[\[\]$.+*?&^|{}()]/', '[$0]', $str), '\\\'');
};

Find: Select
$query_where[] = 'm.body' . (in_array($regularWord, $query_params['excluded_words']) ? ' NOT' : '') . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:complex_body_' . $count . '}';
$query_params['complex_body_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($regularWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $regularWord), '\\\'') . '[[:>:]]';
Replace With: Select
if (in_array($regularWord, $query_params['excluded_words']))
$query_where[] = 'm.body NOT ' . $query_match_type . ' {string:complex_body_' . $count . '}';
else
$query_where[] = 'm.body ' . $query_match_type . ' {string:complex_body_' . $count . '}';

if ($is_search_regex)
$query_params['complex_body_' . $count++] = $word_boundary_wrapper($escape_sql_regex($regularWord));
else
$query_params['complex_body_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($regularWord) . '%';
Find: Select
$query_where[] = 'subject NOT' . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:exclude_subject_phrase_' . $count . '}';
$query_params['exclude_subject_phrase_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($phrase, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $phrase), '\\\'') . '[[:>:]]';
Replace With: Select
$query_where[] = 'subject NOT ' . $query_match_type . ' {string:exclude_subject_phrase_' . $count . '}';

if ($is_search_regex)
$query_params['exclude_subject_phrase_' . $count++] = $word_boundary_wrapper($escape_sql_regex($phrase));
else
$query_params['exclude_subject_phrase_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($phrase) . '%';
Find: Select
$query_where[] = 'subject NOT' . (empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? ' LIKE ' : ' RLIKE ') . '{string:exclude_subject_words_' . $count . '}';
$query_params['exclude_subject_words_' . $count++] = empty($modSettings['search_match_words']) || $search_data['no_regexp'] ? '%' . strtr($excludedWord, array('_' => '\\_', '%' => '\\%')) . '%' : '[[:<:]]' . addcslashes(preg_replace(array('/([\[\]$.+*?|{}()])/'), array('[$1]'), $excludedWord), '\\\'') . '[[:>:]]';
Replace With: Select
$query_where[] = 'subject NOT ' . $query_match_type . ' {string:exclude_subject_words_' . $count . '}';

if ($is_search_regex)
$query_params['exclude_subject_words_' . $count++] = $word_boundary_wrapper($escape_sql_regex($excludedWord));
else
$query_params['exclude_subject_words_' . $count++] = '%' . $smcFunc['db_escape_wildcard_string']($excludedWord) . '%';

./Sources/Security.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
);

Add After: Select
if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'dlattach')
die();

./Sources/Subs-Attachments.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($attachment['filename'])),
Replace With: Select
'name' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars'](un_htmlspecialchars($attachment['filename']))),
Find: Select
'link' => '<a href="' . $scripturl . '?action=dlattach;attach=' . $attachment['id_attach'] . '" class="bbc_link">' . $smcFunc['htmlspecialchars']($attachment['filename']) . '</a>',
Replace With: Select
'link' => '<a href="' . $scripturl . '?action=dlattach;attach=' . $attachment['id_attach'] . '" class="bbc_link">' . $smcFunc['htmlspecialchars'](un_htmlspecialchars($attachment['filename'])) . '</a>',

./Sources/Subs-Auth.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
This operation isn't vital to the installation of this mod.
Find: Select
* @version 2.1.2
Replace With: Select
* @version 2.1.4
Find: Select
global $txt, $context;

Add After: Select
loadTheme();
Find: Select
foreach ($names as $i => $name)
Replace With: Select
foreach (array_values($names) as $i => $name)

./Sources/Subs-Boards.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
// Who's allowed to access this board.
Add After: Select
$board_permissions_inserts = array();
Find: Select
$boardUpdates[] = 'member_groups = {string:member_groups}';
Add Before: Select
foreach ($boardOptions['access_groups'] as $value)
$board_permissions_inserts[] = array($value, $board_id, 0);

Find: Select
$boardUpdates[] = 'deny_member_groups = {string:deny_groups}';
Add Before: Select
foreach ($boardOptions['deny_groups'] as $value)
$board_permissions_inserts[] = array($value, $board_id, 1);

Find: Select
if (!empty($boardOptions['deny_groups']) || !empty($boardOptions['access_groups'])) {
// Before we add new access_groups or deny_groups, remove all of the old entries
$smcFunc['db_query']('', '
DELETE FROM {db_prefix}board_permissions_view
WHERE id_board = {int:selected_board}',
array(
'selected_board' => $board_id,
)
);
}

// Do permission sync
if (!empty($boardOptions['deny_groups']))
{
$insert = array();
foreach ($boardOptions['deny_groups'] as $value)
$insert[] = array($value, $board_id, 1);

$smcFunc['db_insert']('insert',
'{db_prefix}board_permissions_view',
array('id_group' => 'int', 'id_board' => 'int', 'deny' => 'int'),
$insert,
array('id_group', 'id_board', 'deny')
);
}

if (!empty($boardOptions['access_groups']))
{
$insert = array();
foreach ($boardOptions['access_groups'] as $value)
$insert[] = array($value, $board_id, 0);
Replace With: Select
// Before we add new access_groups or deny_groups, remove all of the old entries
$smcFunc['db_query']('', '
DELETE FROM {db_prefix}board_permissions_view
WHERE id_board = {int:selected_board}',
array(
'selected_board' => $board_id,
)
);
Find: Select
$smcFunc['db_insert']('insert',
'{db_prefix}board_permissions_view',
array('id_group' => 'int', 'id_board' => 'int', 'deny' => 'int'),
$insert,
array('id_group', 'id_board', 'deny')
);
}

Replace With: Select
if ($board_permissions_inserts != array())
$smcFunc['db_insert']('insert',
'{db_prefix}board_permissions_view',
array('id_group' => 'int', 'id_board' => 'int', 'deny' => 'int'),
$board_permissions_inserts,
array('id_group', 'id_board', 'deny')
);

Find: Select
$default_memgrps = '-1,0';

$board_columns = array(
Replace With: Select
$board_columns = array(
Find: Select
$default_memgrps, '',
Replace With: Select
'', '',
Find: Select
$insert = array();

foreach (explode(',', $default_memgrps) as $value)
$insert[] = array($value, $board_id, 0);

$smcFunc['db_insert']('',
'{db_prefix}board_permissions_view',
array('id_group' => 'int', 'id_board' => 'int', 'deny' => 'int'),
$insert,
array('id_group', 'id_board', 'deny'),
1
);

if (empty($board_id))
Replace With: Select
if (empty($board_id))

./Sources/Subs-Charset.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
return normalizer_normalize($string, Normalizer::FORM_D);
}

Add After: Select
if (utf8_is_normalized($string, 'd'))
return $string;

Find: Select
return normalizer_normalize($string, Normalizer::FORM_KD);
}

Add After: Select
if (utf8_is_normalized($string, 'kd'))
return $string;

Find: Select
return normalizer_normalize($string, Normalizer::FORM_C);
}

Add After: Select
if (utf8_is_normalized($string, 'c'))
return $string;

Find: Select
return normalizer_normalize($string, Normalizer::FORM_KC);
}

Add After: Select
if (utf8_is_normalized($string, 'kc'))
return $string;

Find: Select
global $sourcedir;

$string = (string) $string;

Add After: Select
if (utf8_is_normalized($string, 'kc_casefold'))
return $string;

Find: Select
/**
* Helper function for utf8_normalize_d and utf8_normalize_kd.
Add Before: Select
/**
* Checks whether a string is already normalized to a given form.
*
* @param string|array $string A string of UTF-8 characters.
* @param string $form One of 'd', 'c', 'kd', 'kc', or 'kc_casefold'
* @return bool Whether the string is already normalized to the given form.
*/
function utf8_is_normalized($string, $form)
{
global $sourcedir;

// Check whether string contains characters that are disallowed in this form.
switch ($form)
{
case 'd':
$prop = 'NFD_QC';
break;

case 'kd':
$prop = 'NFKD_QC';
break;

case 'c':
$prop = 'NFC_QC';
break;

case 'kc':
$prop = 'NFKC_QC';
break;

case 'kc_casefold':
$prop = 'Changes_When_NFKC_Casefolded';
break;

default:
return false;
break;
}

require_once($sourcedir . '/Unicode/QuickCheck.php');
$qc = utf8_regex_quick_check();

if (preg_match('/[' . $qc[$prop] . ']/u', $string))
return false;

// Check whether all combining marks are in canonical order.
// Note: Because PCRE's Unicode data might be outdated compared to ours,
// this regex checks for marks and anything PCRE thinks is not a character.
// That means the more thorough checks will occasionally be performed on
// strings that don't need them, but building and running a perfect regex
// would be more expensive in the vast majority of cases, so meh.
if (preg_match_all('/([\p{M}\p{Cn}])/u', $string, $matches, PREG_OFFSET_CAPTURE))
{
require_once($sourcedir . '/Unicode/CombiningClasses.php');

$combining_classes = utf8_combining_classes();

$last_pos = 0;
$last_len = 0;
$last_ccc = 0;
foreach ($matches[1] as $match)
{
$char = $match[0];
$pos = $match[1];
$ccc = isset($combining_classes[$char]) ? $combining_classes[$char] : 0;

// Not in canonical order, so return false.
if ($pos === $last_pos + $last_len && $ccc > 0 && $last_ccc > $ccc)
return false;

$last_pos = $pos;
$last_len = strlen($char);
$last_ccc = $ccc;
}
}

// If we get here, the string is normalized correctly.
return true;
}

./Sources/Subs-Db-postgresql.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
'insert_log_search_topics' => array(
'~NOT RLIKE~' => '!~',
),
'insert_log_search_results_no_index' => array(
'~NOT RLIKE~' => '!~',
),
'insert_log_search_results_subject' => array(
'~NOT RLIKE~' => '!~',
),
'profile_board_stats' => array(
Replace With: Select
'profile_board_stats' => array(

./Sources/Subs-Editor.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
// Verification question does not exist for this language.
if ($thisVerification['number_questions'] && (!isset($_SESSION[$verificationOptions['id'] . '_vv']['q']) || !isset($_REQUEST[$verificationOptions['id'] . '_vv']['q'])))
fatal_lang_error('registration_no_verification_questions');
// Hmm, it's requested but not actually declared. This shouldn't happen.
Replace With: Select
// Hmm, it's requested but not actually declared. This shouldn't happen.

./Sources/Subs-Membergroups.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
logAction('added_to_group', array('group' => $group_names[$group], 'member_affected' => $member), 'admin');
Replace With: Select
logAction('added_to_group', array('group' => $group_names[$group], 'member' => $member), 'admin');

./Sources/Subs-Menu.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
integrate_pm_areas
integrate_profile_areas
Replace With: Select
integrate_pm_areas
Find: Select
if (!empty($menu_context['current_action']))
Replace With: Select
if (!empty($menu_context['current_action']) && empty($menuOptions['disable_hook_call']))

./Sources/Subs.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
'domain' => '(?:(?P>domain_label_char)+\.)+(?P>tlds)',
Replace With: Select
'domain' => '(?:(?P>domain_label_char)+\.)+(?P>tlds)(?!\.(?P>domain_label_char))',
Find: Select
$pcre_subroutines['excluded_lookahead'] = '(?![' . $excluded_trailing_chars . ']*(?>[\h\v]|<br>|$))';
Replace With: Select
$pcre_subroutines['excluded_lookahead'] = '(?![' . $excluded_trailing_chars . ']*(?' . '>[\h\v]|<br>|$))';
Find: Select
// URI scheme (or lack thereof for schemeless URLs)
'(?:' .
Replace With: Select
// URI scheme (or lack thereof for schemeless URLs)
'(?' . '>' .
Find: Select
'onclick' => 'return reqOverlayDiv(this.href, ' . JavaScriptEscape($txt['login']) . ');',
Replace With: Select
'onclick' => 'return reqOverlayDiv(this.href, ' . JavaScriptEscape($txt['login']) . ', \'login\');',
Find: Select
* does nothing if the function is already added.
Replace With: Select
* Does nothing if the function is already added.
* Cleans up enabled/disabled variants before taking requested action.
Find: Select
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
{
global $smcFunc, $modSettings;
Replace With: Select
function add_integration_function($hook, $function, $permanent = true, $file = '', $object = false)
{
global $smcFunc, $modSettings, $context;
Find: Select

// Is it going to be permanent?
if ($permanent)
{
$request = $smcFunc['db_query']('', '
SELECT value
FROM {db_prefix}settings
WHERE variable = {string:variable}',
array(
'variable' => $hook,
)
);
list ($current_functions) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);

if (!empty($current_functions))
{
$current_functions = explode(',', $current_functions);
if (in_array($integration_call, $current_functions))
return;

Replace With: Select
$enabled_call = rtrim($function, '!');
$disabled_call = $enabled_call . '!';

// Is it going to be permanent?
if ($permanent)
{
$request = $smcFunc['db_query']('', '
SELECT value
FROM {db_prefix}settings
WHERE variable = {string:variable}',
array(
'variable' => $hook,
)
);
list ($current_functions) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);

if (!empty($current_functions))
{
$current_functions = explode(',', $current_functions);

Find: Select
$permanent_functions = array_merge($current_functions, array($integration_call));
}
else
$permanent_functions = array($integration_call);

updateSettings(array($hook => implode(',', $permanent_functions)));
}

// Make current function list usable.
$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);

// Do nothing, if it's already there.
if (in_array($integration_call, $functions))
return;

Replace With: Select
// Cleanup enabled/disabled variants before taking action.
$current_functions = array_diff($current_functions, array($enabled_call, $disabled_call));

$permanent_functions = array_unique(array_merge($current_functions, array($integration_call)));
}
else
$permanent_functions = array($integration_call);

updateSettings(array($hook => implode(',', $permanent_functions)));
}

// Make current function list usable.
$functions = empty($modSettings[$hook]) ? array() : explode(',', $modSettings[$hook]);

// Cleanup enabled/disabled variants before taking action.
$functions = array_diff($functions, array($enabled_call, $disabled_call));

Find: Select
$functions[] = $integration_call;
Replace With: Select
$functions = array_unique(array_merge($functions, array($integration_call)));
Find: Select
}

/**
* Remove an integration hook function.
Add Before: Select

// It is handy to be able to know which hooks are temporary...
if ($permanent !== true)
{
if (!isset($context['integration_hooks_temporary']))
$context['integration_hooks_temporary'] = array();
$context['integration_hooks_temporary'][$hook][$function] = true;
}
Find: Select
* Does nothing if the function is not available.
Add After: Select
* Cleans up enabled/disabled variants before taking requested action.
Find: Select

// Get the permanent functions.
$request = $smcFunc['db_query']('', '
SELECT value
FROM {db_prefix}settings
WHERE variable = {string:variable}',
array(
'variable' => $hook,
)
);
list ($current_functions) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);

if (!empty($current_functions))
{
$current_functions = explode(',', $current_functions);

if (in_array($integration_call, $current_functions))
updateSettings(array($hook => implode(',', array_diff($current_functions, array($integration_call)))));
}
Replace With: Select
$enabled_call = rtrim($function, '!');
$disabled_call = $enabled_call . '!';

// Get the permanent functions.
$request = $smcFunc['db_query']('', '
SELECT value
FROM {db_prefix}settings
WHERE variable = {string:variable}',
array(
'variable' => $hook,
)
);
list ($current_functions) = $smcFunc['db_fetch_row']($request);
$smcFunc['db_free_result']($request);

if (!empty($current_functions))
{
$current_functions = explode(',', $current_functions);

// Cleanup enabled and disabled variants.
$current_functions = array_unique(array_diff($current_functions, array($enabled_call, $disabled_call)));

updateSettings(array($hook => implode(',', $current_functions)));
}
Find: Select
// You can only remove it if it's available.
if (!in_array($integration_call, $functions))
return;

$functions = array_diff($functions, array($integration_call));
$modSettings[$hook] = implode(',', $functions);
}
Replace With: Select
// Cleanup enabled and disabled variants.
$functions = array_unique(array_diff($functions, array($enabled_call, $disabled_call)));

$modSettings[$hook] = implode(',', $functions);
}

./Sources/Subscriptions-PayPal.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
Find: Select
curl_setopt($curl, CURLOPT_POSTFIELDSIZE, 0);
curl_setopt($curl, CURLOPT_POSTFIELDS, $requestString);
Replace With: Select
curl_setopt($curl, CURLOPT_POSTFIELDS, $requestString);

./Sources/Who.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
'lurkalot',
'shadav',
Replace With: Select
'lurkalot',
Find: Select
'Storman™',
Add Before: Select
'shadav',
Find: Select
'Jonathan "vbgamer45" Valentin',
'Michael "Mick." Gomez',
Replace With: Select
'Jonathan "vbgamer45" Valentin',
Find: Select
'NanoSector',
Add Before: Select
'Michael "Mick." Gomez',

./Sources/tasks/CreatePost-Notify.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
WHERE (id_topic = {int:topic} OR id_board = {int:board})
Replace With: Select
WHERE ' . ($type == 'topic' ? 'id_board = {int:board}' : 'id_topic = {int:topic}') . '

./Themes/default/ModerationCenter.template.php

This operation isn't vital to the installation of this mod.
Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
This operation isn't vital to the installation of this mod.
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
This operation isn't vital to the installation of this mod.
Find: Select
<span id="group_requests_toggle" class="', !empty($context['admin_prefs']['mcgr']) ? 'toggle_down' : 'toggle_up', ' floatright" style="display: none;"></span>
Replace With: Select
<span id="group_requests_toggle" class="', !empty($context['admin_prefs']['mcgr']) ? 'toggle_down' : 'toggle_up', ' floatright" title="', empty($context['admin_prefs']['mcgr']) ? $txt['hide'] : $txt['show'], '" style="display: none;"></span>
This operation isn't vital to the installation of this mod.
Find: Select
<span id="watched_users_toggle" class="', !empty($context['admin_prefs']['mcwu']) ? 'toggle_down' : 'toggle_up', ' floatright" style="display: none;"></span>
Replace With: Select
<span id="watched_users_toggle" class="', !empty($context['admin_prefs']['mcwu']) ? 'toggle_down' : 'toggle_up', ' floatright" title="', empty($context['admin_prefs']['mcwu']) ? $txt['hide'] : $txt['show'], '" style="display: none;"></span>
This operation isn't vital to the installation of this mod.
Find: Select
<span id="reported_posts_toggle" class="', !empty($context['admin_prefs']['mcrp']) ? 'toggle_down' : 'toggle_up', ' floatright" style="display: none;"></span>
Replace With: Select
<span id="reported_posts_toggle" class="', !empty($context['admin_prefs']['mcrp']) ? 'toggle_down' : 'toggle_up', ' floatright" title="', empty($context['admin_prefs']['mcrp']) ? $txt['hide'] : $txt['show'], '" style="display: none;"></span>
This operation isn't vital to the installation of this mod.
Find: Select
<span id="reported_users_toggle" class="', !empty($context['admin_prefs']['mcur']) ? 'toggle_down' : 'toggle_up', ' floatright" style="display: none;"></span>
Replace With: Select
<span id="reported_users_toggle" class="', !empty($context['admin_prefs']['mcur']) ? 'toggle_down' : 'toggle_up', ' floatright" title="', empty($context['admin_prefs']['mcur']) ? $txt['hide'] : $txt['show'], '" style="display: none;"></span>

./Themes/default/MoveTopic.template.php

This operation isn't vital to the installation of this mod.
Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
This operation isn't vital to the installation of this mod.
Find: Select
* @version 2.1.0
Replace With: Select
* @version 2.1.4
This operation isn't vital to the installation of this mod.
Find: Select
<label for="postRedirect">
Replace With: Select
<label for="postRedirect" class="block">

./Themes/default/Post.template.php

This operation isn't vital to the installation of this mod.
Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
This operation isn't vital to the installation of this mod.
Find: Select
* @version 2.1.2
Replace With: Select
* @version 2.1.4
Find: Select
global $context, $options, $txt, $scripturl, $modSettings, $counter;
Add After: Select
global $settings;
Find: Select
// If we're editing and displaying edit details, show a box where they can say why
if (isset($context['editing']) && $modSettings['show_modify'])
echo '
<dl>
<dt class="clear">
<span id="caption_edit_reason">', $txt['reason_for_edit'], ':</span>
</dt>
<dd>
<input type="text" name="modify_reason"', isset($context['last_modified_reason']) ? ' value="' . $context['last_modified_reason'] . '"' : '', ' tabindex="', $context['tabindex']++, '" size="80" maxlength="80">
</dd>
</dl>';

// If this message has been edited in the past - display when it was.
if (isset($context['last_modified']))
echo '
<div class="padding smalltext">
', $context['last_modified_text'], '
</div>';

// If the admin has enabled the hiding of the additional options - show a link and image for it.
if (!empty($modSettings['additional_options_collapsable']))
echo '
<div id="post_additional_options_header">
<strong><a href="#" id="postMoreExpandLink"> ', $context['can_post_attachment'] ? $txt['post_additionalopt_attach'] : $txt['post_additionalopt'], '</a></strong>
</div>';

echo '
<div id="post_additional_options">';

// Display the checkboxes for all the standard options - if they are available to the user!
echo '
<div id="post_settings" class="smalltext">
<ul class="post_options">
', $context['can_notify'] ? '<li><input type="hidden" name="notify" value="0"><label for="check_notify"><input type="checkbox" name="notify" id="check_notify"' . ($context['notify'] || !empty($options['auto_notify']) || $context['auto_notify'] ? ' checked' : '') . ' value="1"> ' . $txt['notify_replies'] . '</label></li>' : '', '
', $context['can_lock'] ? '<li><input type="hidden" name="already_locked" value="' . $context['already_locked'] . '"><input type="hidden" name="lock" value="0"><label for="check_lock"><input type="checkbox" name="lock" id="check_lock"' . ($context['locked'] ? ' checked' : '') . ' value="1"> ' . $txt['lock_topic'] . '</label></li>' : '', '
<li><label for="check_back"><input type="checkbox" name="goback" id="check_back"' . ($context['back_to_topic'] || !empty($options['return_to_post']) ? ' checked' : '') . ' value="1"> ' . $txt['back_to_topic'] . '</label></li>
', $context['can_sticky'] ? '<li><input type="hidden" name="already_sticky" value="' . $context['already_sticky'] . '"><input type="hidden" name="sticky" value="0"><label for="check_sticky"><input type="checkbox" name="sticky" id="check_sticky"' . ($context['sticky'] ? ' checked' : '') . ' value="1"> ' . $txt['sticky_after_posting'] . '</label></li>' : '', '
<li><label for="check_smileys"><input type="checkbox" name="ns" id="check_smileys"', $context['use_smileys'] ? '' : ' checked', ' value="NS"> ', $txt['dont_use_smileys'], '</label></li>', '
', $context['can_move'] ? '<li><input type="hidden" name="move" value="0"><label for="check_move"><input type="checkbox" name="move" id="check_move" value="1"' . (!empty($context['move']) ? ' checked" ' : '') . '> ' . $txt['move_after_posting'] . '</label></li>' : '', '
', $context['can_announce'] && $context['is_first_post'] ? '<li><label for="check_announce"><input type="checkbox" name="announce_topic" id="check_announce" value="1"' . (!empty($context['announce']) ? ' checked' : '') . '> ' . $txt['announce_topic'] . '</label></li>' : '', '
', $context['show_approval'] ? '<li><label for="approve"><input type="checkbox" name="approve" id="approve" value="2"' . ($context['show_approval'] === 2 ? ' checked' : '') . '> ' . $txt['approve_this_post'] . '</label></li>' : '', '
</ul>
</div><!-- #post_settings -->';

// If this post already has attachments on it - give information about them.
if (!empty($context['current_attachments']))
{
echo '
<dl id="postAttachment">
<dt>
', $txt['attachments'], ':
</dt>
<dd class="smalltext" style="width: 100%;">
<input type="hidden" name="attach_del[]" value="0">
', $txt['uncheck_unwatchd_attach'], ':
</dd>';

foreach ($context['current_attachments'] as $attachment)
echo '
<dd class="smalltext">
<label for="attachment_', $attachment['attachID'], '"><input type="checkbox" id="attachment_', $attachment['attachID'], '" name="attach_del[]" value="', $attachment['attachID'], '"', empty($attachment['unchecked']) ? ' checked' : '', '> ', $attachment['name'], (empty($attachment['approved']) ? ' (' . $txt['awaiting_approval'] . ')' : ''),
!empty($modSettings['attachmentPostLimit']) || !empty($modSettings['attachmentSizeLimit']) ? sprintf($txt['attach_kb'], comma_format(round(max($attachment['size'], 1024) / 1024), 0)) : '', '</label>
</dd>';

echo '
</dl>';

Replace With: Select
// Show attachments.
if (!empty($context['current_attachments']) || $context['can_post_attachment'])
{
echo '
<div id="post_attachments_area" class="roundframe noup">';

// The non-JavaScript UI.
echo '
<div id="postAttachment">
<div class="padding">
<div>
<strong>', $txt['attachments'], '</strong>:';

if ($context['can_post_attachment'])
echo '
<input type="file" multiple="multiple" name="attachment[]" id="attachment1">
<a href="javascript:void(0);" onclick="cleanFileInput(\'attachment1\');">(', $txt['clean_attach'], ')</a>';

if (!empty($modSettings['attachmentSizeLimit']))
echo '
<input type="hidden" name="MAX_FILE_SIZE" value="' . $modSettings['attachmentSizeLimit'] * 1024 . '">';

echo '
</div>';

if (!empty($context['attachment_restrictions']))
echo '
<div class="smalltext">', $txt['attach_restrictions'], ' ', implode(', ', $context['attachment_restrictions']), '</div>';

echo '
<div class="smalltext">
<input type="hidden" name="attach_del[]" value="0">
', $txt['uncheck_unwatchd_attach'], '
</div>
</div>
<div class="attachments">';

// If this post already has attachments on it - give information about them.
if (!empty($context['current_attachments']))
{
foreach ($context['current_attachments'] as $attachment)
{
echo '
<div class="attached">
<input type="checkbox" id="attachment_', $attachment['attachID'], '" name="attach_del[]" value="', $attachment['attachID'], '"', empty($attachment['unchecked']) ? ' checked' : '', '>';

if (!empty($modSettings['attachmentShowImages']))
{
if (strpos($attachment['mime_type'], 'image') === 0)
$src = $scripturl . '?action=dlattach;attach=' . (!empty($attachment['thumb']) ? $attachment['thumb'] : $attachment['attachID']) . ';preview;image';
else
$src = $settings['images_url'] . '/generic_attach.png';

echo '
<div class="attachments_top">
<img src="', $src, '" alt="" loading="lazy" class="atc_img">
</div>';
}

echo '
<div class="attachments_bot">
<span class="name">' . $attachment['name'] . '</span>', (empty($attachment['approved']) ? '
<br>(' . $txt['awaiting_approval'] . ')' : ''), '
<br>', $attachment['size'] < 1024000 ? round($attachment['size'] / 1024, 2) . ' ' . $txt['kilobyte'] : round($attachment['size'] / 1024 / 1024, 2) . ' ' . $txt['megabyte'], '
</div>
</div>';
}
}

echo '
</div>
</div>';

Find: Select
<div class="smalltext"><em>', $context['files_in_session_warning'], '</em></div>';
}
Replace With: Select
<div class="smalltext"><em>', $context['files_in_session_warning'], '</em></div>';
Find: Select
// Is the user allowed to post any additional ones? If so give them the boxes to do it!
if ($context['can_post_attachment'])
{
// Print dropzone UI.
echo '
<div class="files" id="attachment_previews">
<div>
<strong>', $txt['attachments'], ':</strong>
</div>
<div id="au-template">
<div class="attach-preview">
<img data-dz-thumbnail />
Replace With: Select
// Is the user allowed to post any additional ones? If so give them the boxes to do it!
if ($context['can_post_attachment'])
{
// Print dropzone UI.
echo '
<div id="attachment_upload">
<div id="drop_zone_ui" class="centertext">
<div class="attach_drop_zone_label">
', $context['num_allowed_attachments'] <= count($context['current_attachments']) ? $txt['attach_limit_nag'] : $txt['attach_drop_zone'], '
Find: Select
<div class="attachment_info">
<div>
<span class="name" data-dz-name></span>
<span class="error" data-dz-errormessage></span>
<span class="size" data-dz-size></span>
<span class="message" data-dz-message></span>
</div>
<div class="attached_BBC">
<input type="text" name="attachBBC" value="" readonly>
<div class="attached_BBC_width_height">
<div class="attached_BBC_width">
<label for="attached_BBC_width">', $txt['attached_insert_width'], '</label>
<input type="number" name="attached_BBC_width" min="0" value="" placeholder="auto">
</div>
<div class="attached_BBC_height">
<label for="attached_BBC_height">', $txt['attached_insert_height'], '</label>
<input type="number" name="attached_BBC_height" min="0" value="" placeholder="auto">
Replace With: Select
</div>
<div class="files" id="attachment_previews">
<div id="au-template">
<div class="attachment_preview_wrapper">
<div class="attach-ui roundframe">
<a data-dz-remove class="main_icons delete floatright cancel"></a>
<div class="attached_BBC_width_height">
<div class="attached_BBC_width">
<label for="attached_BBC_width">', $txt['attached_insert_width'], '</label>
<input type="number" name="attached_BBC_width" min="0" value="">
</div>
<div class="attached_BBC_height">
<label for="attached_BBC_height">', $txt['attached_insert_height'], '</label>
<input type="number" name="attached_BBC_height" min="0" value="">
</div>
Find: Select
</div><!-- .attached_BBC -->
<div class="progress_bar" role="progressBar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="bar"></div>
</div>
<div class="attach-ui">
<a data-dz-remove class="button cancel">', $txt['modify_cancel'], '</a>
<a class="button upload">', $txt['upload'], '</a>
</div>
</div><!-- .attachment_info -->
</div><!-- #au-template -->
</div><!-- #attachment_previews -->
<div id ="max_files_progress" class="max_files_progress progress_bar">
<div class="bar"></div>
</div>
<div id ="max_files_progress_text"></div>';

echo '
<dl id="postAttachment2">
<dd class="fallback">
<div id="attachment_upload" class="descbox">
<div id="drop_zone_ui">
<div>
<strong>', $txt['attach_drop_zone'], '</strong>
</div>
<div class="righttext">
<a class="button" id="attach_cancel_all">', $txt['attached_cancel_all'], '</a>
<a class="button" id="attach_upload_all">', $txt['attached_upload_all'], '</a>
<a class="button fileinput-button">', $txt['attach_add'], '</a>
</div>
</div>
<div id="total_progress" class="progress_bar" role="progressBar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="bar"></div>
</div>
<div class="fallback">
<input type="file" multiple="multiple" name="attachment[]" id="attachment1" class="fallback"> (<a href="javascript:void(0);" onclick="cleanFileInput(\'attachment1\');">', $txt['clean_attach'], '</a>)';

if (!empty($modSettings['attachmentSizeLimit']))
echo '
<input type="hidden" name="MAX_FILE_SIZE" value="' . $modSettings['attachmentSizeLimit'] * 1024 . '">';

echo '
</div><!-- .fallback -->
</div><!-- #attachment_upload -->
</dd>';

// Add any template changes for an alternative upload system here.
call_integration_hook('integrate_upload_template');

echo '
<dd class="smalltext">';

// Show some useful information such as allowed extensions, maximum size and amount of attachments allowed.
if (!empty($modSettings['attachmentCheckExtensions']))
echo '
', $txt['allowed_types'], ': ', $context['allowed_extensions'], '<br>';

if (!empty($context['attachment_restrictions']))
echo '
', $txt['attach_restrictions'], ' ', implode(', ', $context['attachment_restrictions']), '<br>';

Replace With: Select
<div class="attach-preview">
<img data-dz-thumbnail />
</div>
<div class="attachment_info">
<span class="name" data-dz-name></span>
<span class="error" data-dz-errormessage></span>
<span class="size" data-dz-size></span>
<span class="message" data-dz-message></span>
<div class="progress_bar" role="progressBar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="bar"></div>
</div>
</div><!-- .attachment_info -->
</div>
</div><!-- #au-template -->
<div class="attachment_spacer">
<div class="fallback">
<input type="file" multiple="multiple" name="attachment[]" id="attachment1" class="fallback"> (<a href="javascript:void(0);" onclick="cleanFileInput(\'attachment1\');">', $txt['clean_attach'], '</a>)';

if (!empty($modSettings['attachmentSizeLimit']))
echo '
<input type="hidden" name="MAX_FILE_SIZE" value="' . $modSettings['attachmentSizeLimit'] * 1024 . '">';

echo '
</div><!-- .fallback -->
</div>
</div><!-- #attachment_previews -->
</div>
<div id="max_files_progress" class="max_files_progress progress_bar" role="progressBar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0">
<div class="bar"></div>
<div id="max_files_progress_text"></div>
</div>';
}

echo '
</div>';
}

Find: Select
if ($context['num_allowed_attachments'] <= count($context['current_attachments']))
echo '
', $txt['attach_limit_nag'], '<br>';

if (!$context['can_post_attachment_unapproved'])
echo '
<span class="alert">', $txt['attachment_requires_approval'], '</span>', '<br>';

echo '
</dd>
</dl>';
}

Replace With: Select
// If the admin has enabled the hiding of the additional options - show a link and image for it.
if (!empty($modSettings['additional_options_collapsable']))
echo '
<div id="post_additional_options_header">
<strong><a href="#" id="postMoreExpandLink"> ', $txt['post_additionalopt'], '</a></strong>
</div>';

echo '
<div id="post_additional_options">';

// Display the checkboxes for all the standard options - if they are available to the user!
echo '
<div id="post_settings" class="smalltext">
<ul class="post_options">
', $context['can_notify'] ? '<li><input type="hidden" name="notify" value="0"><label for="check_notify"><input type="checkbox" name="notify" id="check_notify"' . ($context['notify'] || !empty($options['auto_notify']) || $context['auto_notify'] ? ' checked' : '') . ' value="1"> ' . $txt['notify_replies'] . '</label></li>' : '', '
', $context['can_lock'] ? '<li><input type="hidden" name="already_locked" value="' . $context['already_locked'] . '"><input type="hidden" name="lock" value="0"><label for="check_lock"><input type="checkbox" name="lock" id="check_lock"' . ($context['locked'] ? ' checked' : '') . ' value="1"> ' . $txt['lock_topic'] . '</label></li>' : '', '
<li><label for="check_back"><input type="checkbox" name="goback" id="check_back"' . ($context['back_to_topic'] || !empty($options['return_to_post']) ? ' checked' : '') . ' value="1"> ' . $txt['back_to_topic'] . '</label></li>
', $context['can_sticky'] ? '<li><input type="hidden" name="already_sticky" value="' . $context['already_sticky'] . '"><input type="hidden" name="sticky" value="0"><label for="check_sticky"><input type="checkbox" name="sticky" id="check_sticky"' . ($context['sticky'] ? ' checked' : '') . ' value="1"> ' . $txt['sticky_after_posting'] . '</label></li>' : '', '
<li><label for="check_smileys"><input type="checkbox" name="ns" id="check_smileys"', $context['use_smileys'] ? '' : ' checked', ' value="NS"> ', $txt['dont_use_smileys'], '</label></li>', '
', $context['can_move'] ? '<li><input type="hidden" name="move" value="0"><label for="check_move"><input type="checkbox" name="move" id="check_move" value="1"' . (!empty($context['move']) ? ' checked" ' : '') . '> ' . $txt['move_after_posting'] . '</label></li>' : '', '
', $context['can_announce'] && $context['is_first_post'] ? '<li><label for="check_announce"><input type="checkbox" name="announce_topic" id="check_announce" value="1"' . (!empty($context['announce']) ? ' checked' : '') . '> ' . $txt['announce_topic'] . '</label></li>' : '', '
', $context['show_approval'] ? '<li><label for="approve"><input type="checkbox" name="approve" id="approve" value="2"' . ($context['show_approval'] === 2 ? ' checked' : '') . '> ' . $txt['approve_this_post'] . '</label></li>' : '', '
</ul>
</div><!-- #post_settings -->';

Find: Select
msgExpanded: ', JavaScriptEscape($context['can_post_attachment'] ? $txt['post_additionalopt_attach'] : $txt['post_additionalopt']), ',
msgCollapsed: ', JavaScriptEscape($context['can_post_attachment'] ? $txt['post_additionalopt_attach'] : $txt['post_additionalopt']), '
Replace With: Select
msgExpanded: ', JavaScriptEscape($txt['post_additionalopt']), ',
msgCollapsed: ', JavaScriptEscape($txt['post_additionalopt']), '

./Themes/default/Profile.template.php

This operation isn't vital to the installation of this mod.
Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
This operation isn't vital to the installation of this mod.
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
This operation isn't vital to the installation of this mod.
Find: Select
', $context['update_message'], '.
Replace With: Select
', $context['update_message'], '

./Themes/default/css/admin.css

This operation isn't vital to the installation of this mod.
Find: Select

/* The welcome thingy. */
#welcome {
border: 1px solid #ddd;
border-top: none;
border-radius: 0 0 7px 7px;
font-size: 0.9em;
padding: 12px 9px 8px 9px;
}
#welcome a {
font-weight: bold;
}
#welcome img {
vertical-align: middle;
}
.search_results {
Replace With: Select
.search_results {

./Themes/default/css/index.css

This operation isn't vital to the installation of this mod.
Find: Select
/* Note: The next declarations are for keyboard access with js disabled. */
.dropmenu ul a:focus, .dropmenu ul ul a:focus {
margin-left: 9990px;
border: none;
width: 17em;
}
.dropmenu ul ul a:focus {
margin-left: 19950px;
}
/* Cancel those for hover and/or js access. */
.dropmenu ul li:hover a:focus, .dropmenu ul li a:focus {
margin-left: 0;
width: auto;
}
/* Level 3 submenu wrapper positioning. */
Replace With: Select
/* Level 3 submenu wrapper positioning. */
This operation isn't vital to the installation of this mod.
Find: Select
/* the small stats */
#index_common_stats {
margin: -4px 8px 6px 8px;
padding: 4px 0 0 0;
font-size: 0.9em;
border-top: 1px solid #ddd;
}

.fix_rtl_names {
Replace With: Select
.fix_rtl_names {

./Themes/default/css/jquery.sceditor.css

This operation isn't vital to the installation of this mod.
Find: Select
border-radius: 4px;
Replace With: Select
border-radius: 4px 4px 0 0;
This operation isn't vital to the installation of this mod.
Find: Select
flex-basis: 175px;
Replace With: Select
flex-basis: 250px;
This operation isn't vital to the installation of this mod.
Find: Select
height: 175px;
Replace With: Select
height: 250px;

./Themes/default/css/responsive.css

This operation isn't vital to the installation of this mod.
Find: Select
display: flex;
justify-content: flex-end;
Add After: Select
flex-wrap: wrap;

./Themes/default/languages/Admin.english.php

Find: Select
$txt['hooks_no_hooks'] = 'There are currently no hooks in the system.';
Add Before: Select
$txt['hooks_temp'] = 'Temporary';
Find: Select
$txt['hooks_disable_legend_missing'] = 'the hook has not been found';
Add After: Select
$txt['hooks_disable_legend_temp'] = 'the hook is temporary';
$txt['hooks_disable_legend_temp_missing'] = 'temporary hook not found';

./Themes/default/languages/Errors.english.php

Find: Select
// Version: 2.1.3; Errors
Replace With: Select
// Version: 2.1.4; Errors
Find: Select
$txt['registration_no_secret_question'] = 'Sorry, there is no secret question set for this member.';
$txt['registration_no_verification_questions'] = 'Verification questions not configured properly. Please report this error to an administrator.';
Replace With: Select
$txt['registration_no_secret_question'] = 'Sorry, there is no secret question set for this member.';

./Themes/default/languages/Post.english.php

This operation isn't vital to the installation of this mod.
Find: Select
// Version: 2.1.2; Post
Replace With: Select
// Version: 2.1.4; Post
Find: Select
$txt['attach_drop_zone'] = 'Drag and drop your files here, or use the button to add files.';
Replace With: Select
$txt['attach_drop_zone'] = 'Click or drag files here to attach them.';

./Themes/default/scripts/jquery.sceditor.smf.js

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
attribs += ' data-attachment="' + id + '"'
Replace With: Select
attribs += ' data-type="attachment" data-attachment="' + id + '"';

./Themes/default/scripts/mentions.js

Find: Select
show_the_at: true,
Add After: Select
startWithSpace: true,
Find: Select
matcher: function(flag, subtext, should_start_with_space) {
var match = '', started = false;
var string = subtext.split('');
for (var i = 0; i < string.length; i++)
{
if (string[i] == flag && (!should_start_with_space || i == 0 || /[\s\n]/gi.test(string[i - 1])))
{
started = true;
match = '';
}
else if (started)
match = match + string[i];
}

if (match.length > 0)
return match;

return null;
},
remoteFilter: function (query, callback) {
Replace With: Select
remoteFilter: function (query, callback) {
Find: Select
$(iframe.contentDocument.body).atwho(atwhoConfig);
});
Replace With: Select
$(iframe.contentDocument.body).atwho(atwhoConfig);
});

./Themes/default/scripts/script.js

Find: Select
if (this.opt.aSwapImages[i].isCSS)
{
$('#' + this.opt.aSwapImages[i].sId).toggleClass(this.opt.aSwapImages[i].cssCollapsed, bCollapse).toggleClass(this.opt.aSwapImages[i].cssExpanded, !bCollapse).attr('title', bCollapse ? this.opt.aSwapImages[i].altCollapsed : this.opt.aSwapImages[i].altExpanded);
Add Before: Select
this.opt.aSwapImages[i].altExpanded = this.opt.aSwapImages[i].altExpanded ? this.opt.aSwapImages[i].altExpanded : smf_collapseAlt;
this.opt.aSwapImages[i].altCollapsed = this.opt.aSwapImages[i].altCollapsed ? this.opt.aSwapImages[i].altCollapsed : smf_expandAlt;

./Themes/default/scripts/smf_fileUpload.js

Find: Select
if (typeof current_board == 'undefined')
Add Before: Select
var isNewTemplate = !!document.getElementById('post_attachments_area');

Find: Select
autoQueue: false,
clickable: '.fileinput-button',
Replace With: Select
autoQueue: isNewTemplate,
clickable: isNewTemplate ? ['.attachment_spacer', '#drop_zone_ui'] : '.fileinput-button',
Find: Select
// 3 basic colors.
Add Before: Select
if (isNewTemplate && maxSize > 1024) {
maxSize = Math.round(((maxSize / 1024) + Number.EPSILON) * 100) / 100;
currentSize = Math.round(((currentSize / 1024) + Number.EPSILON) * 10) / 10;
}

Find: Select
if ((myDropzone.options.maxFileAmount != null) && (myDropzone.getAcceptedFiles().length) >= myDropzone.options.maxFileAmount)
Add After: Select
{
$('.attach_drop_zone_label').text(myDropzone.options.text_attachLimitNag);
Find: Select
done(this.options.dictMaxFilesExceeded);
Add After: Select
}
else
$('.attach_drop_zone_label').text(myDropzone.options.text_attachDropzoneLabel);
Find: Select
myDropzone.on('addedfile', function (file) {
Add Before: Select
// Highlight the dropzone target as soon as a file is dragged onto the window.
if (isNewTemplate)
{
var dragTimer;
$(document).on('dragover', function(e) {
var dt = e.originalEvent.dataTransfer;
if (dt.types && (dt.types.indexOf ? dt.types.indexOf('Files') != -1 : dt.types.contains('Files'))) {
$("#attachment_upload").addClass('dz-drag-hover');
window.clearTimeout(dragTimer);
}
});
$(document).on('dragleave dragend', function(e) {
dragTimer = window.setTimeout(function() {
$("#attachment_upload").removeClass('dz-drag-hover');
}, 25);
});
}

Find: Select
_thisElement.find('.attach-ui').fadeIn();
Replace With: Select
_thisElement.find('.attach-ui').show();
Find: Select
insertButton = $('<a />')
.addClass('button')
.addClass('insertBBC')
.prop('disabled', false)
.text(myDropzone.options.text_insertBBC)
.on('click', function (e) {
e.preventDefault();

w = _innerElement.find('input[name="attached_BBC_width"]').val();
h = _innerElement.find('input[name="attached_BBC_height"]').val();

// Get the editor stuff.
var e = $('#' + oEditorID).get(0);
var oEditor = sceditor.instance(e);

oEditor.insert(myDropzone.options.smf_insertBBC(response, w, h), ' ');
})
.appendTo(_innerElement.find('.attach-ui'));
Replace With: Select
// Backward compatibility for themes based on the pre-2.1.4 templates.
if (!isNewTemplate) {
insertButton = $('<a />')
.addClass('button')
.addClass('insertBBC')
.prop('disabled', false)
.text(myDropzone.options.text_insertBBC)
.on('click', function (e) {
e.preventDefault();

w = _innerElement.find('input[name="attached_BBC_width"]').val();
h = _innerElement.find('input[name="attached_BBC_height"]').val();

// Get the editor stuff.
var e = $('#' + oEditorID).get(0);
var oEditor = sceditor.instance(e);

oEditor.insert(myDropzone.options.smf_insertBBC(response, w, h), ' ');
})
.appendTo(_innerElement.find('.attach-ui'));
}
// Insert as an image.
else if (file.type.match(/image.*/)) {
let attached_BBC_width_height = _innerElement.find('.attached_BBC_width_height');

insertPanelButton = $('<a />')
.addClass('main_icons')
.addClass('select_above')
.addClass('floatright')
.addClass('insertBBC')
.prop('disabled', false)
.prop('title', myDropzone.options.text_insertBBC)
.on('click', function (e) {
attached_BBC_width_height.toggle();
})
.insertBefore(attached_BBC_width_height);

insertButton = $('<a />')
.addClass('button')
.addClass('insertBBC')
.addClass('floatright')
.prop('disabled', false)
.text(myDropzone.options.text_insertBBC)
.on('click', function (e) {
e.preventDefault();

w = _innerElement.find('input[name="attached_BBC_width"]').val();
h = _innerElement.find('input[name="attached_BBC_height"]').val();

// Get the editor stuff.
var e = $('#' + oEditorID).get(0);
var oEditor = sceditor.instance(e);

oEditor.insert(myDropzone.options.smf_insertBBC(response, w, h), '');

attached_BBC_width_height.hide();
})
.appendTo(attached_BBC_width_height);
}
// Insert as a plain link.
else {
insertButton = $('<a />')
.addClass('main_icons')
.addClass('select_above')
.addClass('floatright')
.addClass('insertBBC')
.prop('disabled', false)
.prop('title', myDropzone.options.text_insertBBC)
.on('click', function (e) {
e.preventDefault();

// Get the editor stuff.
var e = $('#' + oEditorID).get(0);
var oEditor = sceditor.instance(e);

oEditor.insert(myDropzone.options.smf_insertBBC(response, null, null), ' ');
})
.appendTo(_innerElement.find('.attach-ui'));
}
Find: Select
file.deleteAttachment = function (_innerElement, attachmentId, file) {

Replace With: Select
file.deleteAttachment = function (_innerElement, attachmentId, file) {
Find: Select
deleteButton = $('<a />')
.addClass('button')
Replace With: Select
deleteButton = $('<a />')
.addClass(!isNewTemplate ? 'button' : 'main_icons delete floatright')
Find: Select
.text(myDropzone.options.text_deleteAttach)
Replace With: Select
.prop('title', myDropzone.options.text_deleteAttach)
.text(!isNewTemplate ? myDropzone.options.text_deleteAttach : '')
Find: Select
$this.fadeOutAndRemove();
Replace With: Select
if (!isNewTemplate)
$this.fadeOutAndRemove();
Find: Select
success: function (data, textStatus, xhr) {
Add After: Select
if (!isNewTemplate) {
// For dramatic purposes only!
_innerElement.removeClass('infobox').addClass(data.type + 'box');
Find: Select
// For dramatic purposes only!
_innerElement.removeClass('infobox').addClass(data.type + 'box');

// Remove the text fields and insert button.
_innerElement.find('.attached_BBC').fadeOut();
_innerElement.find('.attachment_info a.insertBBC').fadeOut();
Replace With: Select
// Remove the text fields and insert button.
_innerElement.find('.attached_BBC').fadeOut();
_innerElement.find('.attachment_info a.insertBBC').fadeOut();
}
Find: Select
myDropzone.options.hideFileProgressAndAllButtonsIfNeeded();
Add Before: Select
// Check against the max amount of files setting.
if (myDropzone.getAcceptedFiles().length >= myDropzone.options.maxFileAmount)
{
$('.attach_drop_zone_label').text(myDropzone.options.text_attachLimitNag);
}
else
$('.attach_drop_zone_label').text(myDropzone.options.text_attachDropzoneLabel);

Find: Select
myDropzone.options.hideFileProgressAndAllButtonsIfNeeded();
Add After: Select

if (isNewTemplate)
_innerElement.remove();
Find: Select
})
.appendTo(_innerElement.find('.attach-ui'));
Replace With: Select
});

if (!isNewTemplate)
deleteButton.appendTo(_innerElement.find('.attach-ui'));
else
deleteButton.prependTo(_innerElement.find('.attach-ui'));

// Check against the max amount of files setting.
if (myDropzone.getAcceptedFiles().length >= myDropzone.options.maxFileAmount)
{
$('.attach_drop_zone_label').text(myDropzone.options.text_attachLimitNag);
}
else
$('.attach_drop_zone_label').text(myDropzone.options.text_attachDropzoneLabel);
Find: Select
// If there wasn't any error, change the current cover.
_thisElement.addClass('infobox').removeClass('descbox');
Replace With: Select
// If there wasn't any error, change the current cover.
_thisElement.removeClass('descbox');
if (!isNewTemplate)
_thisElement.addClass('infobox');
Find: Select
_thisElement.addClass('infobox').removeClass('descbox');
Replace With: Select
_thisElement.removeClass('descbox');
if (!isNewTemplate)
_thisElement.addClass('infobox');
Find: Select
_thisElement.find('a.cancel').fadeOutAndRemove();
Replace With: Select
_thisElement.find('a.cancel').remove();
Find: Select
$('#attachment_previews').show();
Replace With: Select
$('#attachment_previews').css('display', !isNewTemplate ? 'block' : 'flex');
Find: Select
$('#drop_zone_ui').show();
Replace With: Select
$('#drop_zone_ui').css('display', !isNewTemplate ? 'block' : 'flex');

./cron.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
define('SMF_VERSION', '2.1.3');
Replace With: Select
define('SMF_VERSION', '2.1.4');
Find: Select
define('SMF_SOFTWARE_YEAR', '2022');
Replace With: Select
define('SMF_SOFTWARE_YEAR', '2023');
Find: Select
define('JQUERY_VERSION', '3.6.0');
Replace With: Select
define('JQUERY_VERSION', '3.6.3');
Find: Select
obExit_cron();
exit;
Add Before: Select

// If we have time, check the scheduled tasks.
if (time() - TIME_START < ceil(MAX_CRON_TIME / 2))
{
require_once($sourcedir . '/ScheduledTasks.php');

if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
AutoTask();
elseif (!empty($modSettings['mail_next_send']) && $modSettings['mail_next_send'] < time())
ReduceMailQueue();
}

./index.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
define('SMF_VERSION', '2.1.3');
Replace With: Select
define('SMF_VERSION', '2.1.4');
Find: Select
define('SMF_SOFTWARE_YEAR', '2022');
Replace With: Select
define('SMF_SOFTWARE_YEAR', '2023');
Find: Select
define('JQUERY_VERSION', '3.6.0');
Replace With: Select
define('JQUERY_VERSION', '3.6.3');
Find: Select
// Before we get carried away, are we doing a scheduled task? If so save CPU cycles by jumping out!
if (isset($_GET['scheduled']))
{
require_once($sourcedir . '/ScheduledTasks.php');
AutoTask();
}

// Check if compressed output is enabled, supported, and not already being done.
Replace With: Select
// Check if compressed output is enabled, supported, and not already being done.

./proxy.php

Find: Select
* @copyright 2022 Simple Machines and individual contributors
Replace With: Select
* @copyright 2023 Simple Machines and individual contributors
Find: Select
* @version 2.1.3
Replace With: Select
* @version 2.1.4
Find: Select
define('SMF_VERSION', '2.1.3');
Replace With: Select
define('SMF_VERSION', '2.1.4');
Find: Select
define('SMF_SOFTWARE_YEAR', '2022');
Replace With: Select
define('SMF_SOFTWARE_YEAR', '2023');
Find: Select
define('JQUERY_VERSION', '3.6.0');
Replace With: Select
define('JQUERY_VERSION', '3.6.3');

Code

auto_1.php
This file should not be able to execute standalone.You may have to run the following queries manually.
Query: Select
<?php updateSettings(array('smfVersion' => '2.1.4'));

File Operations

Move the included file "QuickCheck.php" to "./Sources/Unicode/".
Move the included file "attachments.css" to "./Themes/default/css/".
Move the included file "jquery-3.6.3.min.js" to "./Themes/default/scripts/".
Advertisement: