diff --git a/Languages/en_US/Admin.php b/Languages/en_US/Admin.php
index 0710ebc8ef..a5187c6718 100644
--- a/Languages/en_US/Admin.php
+++ b/Languages/en_US/Admin.php
@@ -778,7 +778,9 @@
$txt['board_perms_deny'] = 'Deny';
$txt['all_boards_in_cat'] = 'All boards in this category';
-$txt['likes_like'] = 'Membergroups allowed to like posts';
+$txt['reacts_react'] = 'Membergroups allowed to react to posts';
+$txt['reactions_manage'] = 'Reactions';
+$txt['manage_reactions_desc'] = 'This area allows you to manage reactions';
$txt['mention'] = 'Membergroups allowed to mention users';
diff --git a/Languages/en_US/Alerts.php b/Languages/en_US/Alerts.php
index cfd95eaf5b..7aadea9612 100644
--- a/Languages/en_US/Alerts.php
+++ b/Languages/en_US/Alerts.php
@@ -26,7 +26,7 @@
$txt['alert_topic_unapproved_reply'] = '{member_link} replied to your unapproved topic, {topic_msg}, in {board_msg}';
$txt['alert_msg_quote'] = '{member_link} quoted you in {msg_msg}';
$txt['alert_msg_mention'] = '{member_link} mentioned you in {msg_msg}';
-$txt['alert_msg_like'] = '{member_link} liked your post, {msg_msg}';
+$txt['alert_msg_react'] = '{member_link} reacted to your post, {msg_msg}';
$txt['alert_msg_report'] = '{member_link} reported the post {msg_msg}';
$txt['alert_msg_report_reply'] = '{member_link} replied to the report about {msg_msg}';
$txt['alert_member_report'] = '{member_link} reported the profile of {profile_msg}';
diff --git a/Languages/en_US/General.php b/Languages/en_US/General.php
index 945e8854f9..6265471d2a 100644
--- a/Languages/en_US/General.php
+++ b/Languages/en_US/General.php
@@ -812,25 +812,29 @@
// Mentions
$txt['mentions'] = 'Mentions';
-// Likes
-$txt['likes'] = 'Likes';
+// Reactions. Previously "likes".
+$txt['reactions'] = 'Reactions';
+// Leave these two for now - "like" will still be the default
$txt['like'] = 'Like';
$txt['unlike'] = 'Unlike';
-$txt['like_success'] = 'Your content was successfully liked.';
-$txt['like_delete'] = 'Your content was successfully deleted.';
-$txt['like_insert'] = 'Your content was successfully inserted.';
-$txt['like_error'] = 'There was an error with your request.';
-$txt['like_disable'] = 'Likes feature is disabled.';
-$txt['not_valid_like_type'] = 'The liked type is not a valid type.';
-$txt['likes_count'] = '{num, plural,
- one {# person likes this.}
- other {# people like this.}
+
+// Todo - i18n this? Maybe react_{type}_success?
+$txt['react_success'] = 'You succesfully reacted to the content ';
+$txt['react_delete'] = 'Your content was successfully deleted.';
+$txt['react_insert'] = 'Your content was successfully inserted.';
+$txt['react_error'] = 'There was an error with your request.';
+$txt['reactions_disable'] = 'Reactions feature is disabled.';
+$txt['not_valid_react_type'] = 'The reacted type is not a valid type.';
+$txt['reactions_count'] = '{num, plural,
+ one {# person reacted to this.}
+ other {# people reacted to this.}
}';
-$txt['you_likes_count'] = '{num, plural,
- =0 {You like this.}
- one {You and # other person like this.}
- other {You and # other people like this.}
+$txt['you_reactions_count'] = '{num, plural,
+ =0 {You reacted to this.}
+ one {You and # other person reacted to this.}
+ other {You and # other people reacted to this.}
}';
+$txt['invalid_reaction'] = 'Invalid reaction ID';
$txt['report_to_mod'] = 'Report to moderator';
$txt['report_profile'] = 'Report profile of {member_name}';
diff --git a/Languages/en_US/ManagePermissions.php b/Languages/en_US/ManagePermissions.php
index 3b5d5fdd09..969f650f75 100644
--- a/Languages/en_US/ManagePermissions.php
+++ b/Languages/en_US/ManagePermissions.php
@@ -241,9 +241,9 @@
$txt['permissionname_report_any'] = 'Report posts to the moderators';
$txt['permissionhelp_report_any'] = 'This permission adds a link to each message, allowing a user to report a post to a moderator. On reporting, all moderators on that board will receive an email with a link to the reported post and a description of the problem (as given by the reporting user).';
-$txt['permissiongroup_likes'] = 'Likes';
-$txt['permissionname_likes_like'] = 'Can like any content';
-$txt['permissionhelp_likes_like'] = 'This permission allows a user to like any content. Users are not allowed to like their own content.';
+$txt['permissiongroup_reactions'] = 'Reactions';
+$txt['permissionname_reactions_react'] = 'Can react to any content';
+$txt['permissionhelp_reactions_react'] = 'This permission allows a user to react to any content. Users are not allowed to react to their own content.';
$txt['permissiongroup_mentions'] = 'Mentions';
$txt['permissionname_mention'] = 'Mention others via @name';
diff --git a/Languages/en_US/ManageReactions.php b/Languages/en_US/ManageReactions.php
new file mode 100644
index 0000000000..0df06215bf
--- /dev/null
+++ b/Languages/en_US/ManageReactions.php
@@ -0,0 +1,19 @@
+
\ No newline at end of file
diff --git a/Languages/en_US/ManageSettings.php b/Languages/en_US/ManageSettings.php
index 79b6b866cf..184ee6a018 100644
--- a/Languages/en_US/ManageSettings.php
+++ b/Languages/en_US/ManageSettings.php
@@ -113,8 +113,8 @@
$txt['force_ssl_complete'] = 'Force SSL throughout the forum';
$txt['search_language'] = 'Fulltext Search Language';
-// Like settings.
-$txt['enable_likes'] = 'Enable Likes';
+// Reaction settings.
+$txt['enable_reacts'] = 'Enable Reactions';
// Mention settings.
$txt['enable_mentions'] = 'Enable Mentions';
diff --git a/Languages/en_US/Profile.php b/Languages/en_US/Profile.php
index 11658bbd12..4cbcd722d6 100644
--- a/Languages/en_US/Profile.php
+++ b/Languages/en_US/Profile.php
@@ -139,7 +139,7 @@
$txt['alert_board_notify'] = 'When a new topic is created in a board I follow, I normally want to know via...';
$txt['alert_msg_mention'] = 'When my @name is mentioned in a post';
$txt['alert_msg_quote'] = 'When one of my posts is quoted';
-$txt['alert_msg_like'] = 'When one of my posts is liked';
+$txt['alert_msg_react'] = 'When someoe reacts to one of my posts';
$txt['alert_unapproved_reply'] = 'When a reply is made to my unapproved topic';
$txt['alert_group_pm'] = 'Personal Messages';
$txt['alert_pm_new'] = 'When I receive a new personal message';
diff --git a/Languages/en_US/Stats.php b/Languages/en_US/Stats.php
index 8a49c5aeed..017ebeb0e0 100644
--- a/Languages/en_US/Stats.php
+++ b/Languages/en_US/Stats.php
@@ -21,8 +21,8 @@
$txt['top_starters'] = 'Top Topic Starters';
$txt['top_time_online'] = 'Most Time Online';
$txt['stats_more_detailed'] = 'more detailed »';
-$txt['top_liked_messages'] = 'Top liked messages';
-$txt['top_liked_users'] = 'Top liked users';
+$txt['top_reacted_messages'] = 'Top reacted messages';
+$txt['top_reacted_users'] = 'Top reacted users';
$txt['average_members'] = 'Average registrations per day';
$txt['average_posts'] = 'Average posts per day';
diff --git a/Sources/Actions/Admin/ACP.php b/Sources/Actions/Admin/ACP.php
index afa160dd2f..56e8d514ca 100644
--- a/Sources/Actions/Admin/ACP.php
+++ b/Sources/Actions/Admin/ACP.php
@@ -1,1998 +1,2011 @@
[
- 'title' => 'admin_main',
- 'permission' => [
- 'admin_forum',
- 'manage_permissions',
- 'moderate_forum',
- 'manage_membergroups',
- 'manage_bans',
- 'send_mail',
- 'edit_news',
- 'manage_boards',
- 'manage_smileys',
- 'manage_attachments',
- ],
- 'areas' => [
- 'index' => [
- 'label' => 'admin_center',
- 'function' => __NAMESPACE__ . '\\Home::call',
- 'icon' => 'administration',
- ],
- 'credits' => [
- 'label' => 'support_credits_title',
- 'function' => __NAMESPACE__ . '\\Home::call',
- 'icon' => 'support',
+
+ declare(strict_types=1);
+
+ namespace SMF\Actions\Admin;
+
+ use SMF\ActionInterface;
+ use SMF\Actions\MessageIndex;
+ use SMF\Actions\Notify;
+ use SMF\ActionTrait;
+ use SMF\BBCodeParser;
+ use SMF\Cache\CacheApi;
+ use SMF\Config;
+ use SMF\Db\DatabaseApi as Db;
+ use SMF\ErrorHandler;
+ use SMF\IntegrationHook;
+ use SMF\Lang;
+ use SMF\Mail;
+ use SMF\Menu;
+ use SMF\SecurityToken;
+ use SMF\Theme;
+ use SMF\Url;
+ use SMF\User;
+ use SMF\Utils;
+
+ /**
+ * This class, unpredictable as this might be, handles basic administration.
+ */
+ class ACP implements ActionInterface
+ {
+ use ActionTrait;
+
+ /*******************
+ * Public properties
+ *******************/
+
+ /**
+ * @var array
+ *
+ * Defines the menu structure for the admin center.
+ * See {@link Menu.php Menu.php} for details!
+ *
+ * The values of all 'title' and 'label' elements are Lang::$txt keys, and
+ * will be replaced at runtime with the values of those Lang::$txt strings.
+ *
+ * All occurrences of '{scripturl}' and '{boardurl}' in value strings will
+ * be replaced at runtime with the real values of Config::$scripturl and
+ * Config::$boardurl.
+ *
+ * In this default definition, all parts of the menu are set as enabled.
+ * At runtime, however, various parts may be turned on or off depending on
+ * the forum's saved settings.
+ *
+ * MOD AUTHORS: If your mod has just a few simple settings and doesn't need
+ * its own settings page, you don't need to bother adding anything to this
+ * menu. Instead, just use the integrate_general_mod_settings hook to add
+ * your settings to the general mod settings page.
+ *
+ * Alternatively, if you want to add a custom settings page for your mod,
+ * please use the integrate_admin_areas hook to add your settings page to
+ * $admin_areas['config']['areas']['modsettings']['subsections'].
+ */
+ public array $admin_areas = [
+ 'forum' => [
+ 'title' => 'admin_main',
+ 'permission' => [
+ 'admin_forum',
+ 'manage_permissions',
+ 'moderate_forum',
+ 'manage_membergroups',
+ 'manage_bans',
+ 'send_mail',
+ 'edit_news',
+ 'manage_boards',
+ 'manage_smileys',
+ 'manage_attachments',
],
- 'news' => [
- 'label' => 'news_title',
- 'function' => __NAMESPACE__ . '\\News::call',
- 'icon' => 'news',
- 'permission' => [
- 'edit_news',
- 'send_mail',
- 'admin_forum',
+ 'areas' => [
+ 'index' => [
+ 'label' => 'admin_center',
+ 'function' => __NAMESPACE__ . '\\Home::call',
+ 'icon' => 'administration',
],
- 'subsections' => [
- 'editnews' => [
- 'label' => 'admin_edit_news',
- 'permission' => 'edit_news',
- ],
- 'mailingmembers' => [
- 'label' => 'admin_newsletters',
- 'permission' => 'send_mail',
- ],
- 'settings' => [
- 'label' => 'settings',
- 'permission' => 'admin_forum',
- ],
+ 'credits' => [
+ 'label' => 'support_credits_title',
+ 'function' => __NAMESPACE__ . '\\Home::call',
+ 'icon' => 'support',
],
- ],
- 'packages' => [
- 'label' => 'package',
- 'function' => 'SMF\\PackageManager\\PackageManager::call',
- 'permission' => ['admin_forum'],
- 'icon' => 'packages',
- 'subsections' => [
- 'browse' => [
- 'label' => 'browse_packages',
- ],
- 'packageget' => [
- 'label' => 'download_packages',
- 'url' => '{scripturl}?action=admin;area=packages;sa=packageget;get',
+ 'news' => [
+ 'label' => 'news_title',
+ 'function' => __NAMESPACE__ . '\\News::call',
+ 'icon' => 'news',
+ 'permission' => [
+ 'edit_news',
+ 'send_mail',
+ 'admin_forum',
],
- 'perms' => [
- 'label' => 'package_file_perms',
+ 'subsections' => [
+ 'editnews' => [
+ 'label' => 'admin_edit_news',
+ 'permission' => 'edit_news',
+ ],
+ 'mailingmembers' => [
+ 'label' => 'admin_newsletters',
+ 'permission' => 'send_mail',
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ 'permission' => 'admin_forum',
+ ],
],
- 'options' => [
- 'label' => 'package_settings',
+ ],
+ 'packages' => [
+ 'label' => 'package',
+ 'function' => 'SMF\\PackageManager\\PackageManager::call',
+ 'permission' => ['admin_forum'],
+ 'icon' => 'packages',
+ 'subsections' => [
+ 'browse' => [
+ 'label' => 'browse_packages',
+ ],
+ 'packageget' => [
+ 'label' => 'download_packages',
+ 'url' => '{scripturl}?action=admin;area=packages;sa=packageget;get',
+ ],
+ 'perms' => [
+ 'label' => 'package_file_perms',
+ ],
+ 'options' => [
+ 'label' => 'package_settings',
+ ],
],
],
- ],
- 'search' => [
- 'function' => __NAMESPACE__ . '\\Find::call',
- 'permission' => ['admin_forum'],
- 'select' => 'index',
- ],
- 'adminlogoff' => [
- 'label' => 'admin_logoff',
- 'function' => __NAMESPACE__ . '\\EndSession::call',
- 'enabled' => true,
- 'icon' => 'exit',
+ 'search' => [
+ 'function' => __NAMESPACE__ . '\\Find::call',
+ 'permission' => ['admin_forum'],
+ 'select' => 'index',
+ ],
+ 'adminlogoff' => [
+ 'label' => 'admin_logoff',
+ 'function' => __NAMESPACE__ . '\\EndSession::call',
+ 'enabled' => true,
+ 'icon' => 'exit',
+ ],
],
],
- ],
- 'config' => [
- 'title' => 'admin_config',
- 'permission' => ['admin_forum'],
- 'areas' => [
- 'featuresettings' => [
- 'label' => 'modSettings_title',
- 'function' => __NAMESPACE__ . '\\Features::call',
- 'icon' => 'features',
- 'subsections' => [
- 'basic' => [
- 'label' => 'mods_cat_features',
- ],
- 'bbc' => [
- 'label' => 'manageposts_bbc_settings',
- ],
- 'layout' => [
- 'label' => 'mods_cat_layout',
- ],
- 'sig' => [
- 'label' => 'signature_settings_short',
- ],
- 'profile' => [
- 'label' => 'custom_profile_shorttitle',
- ],
- 'likes' => [
- 'label' => 'likes',
- ],
- 'mentions' => [
- 'label' => 'mentions',
- ],
- 'alerts' => [
- 'label' => 'notifications',
+ 'config' => [
+ 'title' => 'admin_config',
+ 'permission' => ['admin_forum'],
+ 'areas' => [
+ 'featuresettings' => [
+ 'label' => 'modSettings_title',
+ 'function' => __NAMESPACE__ . '\\Features::call',
+ 'icon' => 'features',
+ 'subsections' => [
+ 'basic' => [
+ 'label' => 'mods_cat_features',
+ ],
+ 'bbc' => [
+ 'label' => 'manageposts_bbc_settings',
+ ],
+ 'layout' => [
+ 'label' => 'mods_cat_layout',
+ ],
+ 'sig' => [
+ 'label' => 'signature_settings_short',
+ ],
+ 'profile' => [
+ 'label' => 'custom_profile_shorttitle',
+ ],
+ 'mentions' => [
+ 'label' => 'mentions',
+ ],
+ 'alerts' => [
+ 'label' => 'notifications',
+ ],
],
],
- ],
- 'antispam' => [
- 'label' => 'antispam_title',
- 'function' => __NAMESPACE__ . '\\AntiSpam::call',
- 'icon' => 'security',
- ],
- 'languages' => [
- 'label' => 'language_configuration',
- 'function' => __NAMESPACE__ . '\\Languages::call',
- 'icon' => 'languages',
- 'subsections' => [
- 'edit' => [
- 'label' => 'language_edit',
- ],
- 'add' => [
- 'label' => 'language_add',
- ],
- 'settings' => [
- 'label' => 'language_settings',
- ],
+ 'antispam' => [
+ 'label' => 'antispam_title',
+ 'function' => __NAMESPACE__ . '\\AntiSpam::call',
+ 'icon' => 'security',
],
- ],
- 'current_theme' => [
- 'label' => 'theme_current_settings',
- 'function' => __NAMESPACE__ . '\\Themes::call',
- 'custom_url' => '{scripturl}?action=admin;area=theme;sa=list;th=%1$d',
- 'icon' => 'current_theme',
- ],
- 'theme' => [
- 'label' => 'theme_admin',
- 'function' => __NAMESPACE__ . '\\Themes::call',
- 'custom_url' => '{scripturl}?action=admin;area=theme',
- 'icon' => 'themes',
- 'subsections' => [
- 'admin' => [
- 'label' => 'themeadmin_admin_title',
- ],
- 'list' => [
- 'label' => 'themeadmin_list_title',
+ 'languages' => [
+ 'label' => 'language_configuration',
+ 'function' => __NAMESPACE__ . '\\Languages::call',
+ 'icon' => 'languages',
+ 'subsections' => [
+ 'edit' => [
+ 'label' => 'language_edit',
+ ],
+ 'add' => [
+ 'label' => 'language_add',
+ ],
+ 'settings' => [
+ 'label' => 'language_settings',
+ ],
],
- 'reset' => [
- 'label' => 'themeadmin_reset_title',
- ],
- 'edit' => [
- 'label' => 'themeadmin_edit_title',
+ ],
+ 'current_theme' => [
+ 'label' => 'theme_current_settings',
+ 'function' => __NAMESPACE__ . '\\Themes::call',
+ 'custom_url' => '{scripturl}?action=admin;area=theme;sa=list;th=%1$d',
+ 'icon' => 'current_theme',
+ ],
+ 'theme' => [
+ 'label' => 'theme_admin',
+ 'function' => __NAMESPACE__ . '\\Themes::call',
+ 'custom_url' => '{scripturl}?action=admin;area=theme',
+ 'icon' => 'themes',
+ 'subsections' => [
+ 'admin' => [
+ 'label' => 'themeadmin_admin_title',
+ ],
+ 'list' => [
+ 'label' => 'themeadmin_list_title',
+ ],
+ 'reset' => [
+ 'label' => 'themeadmin_reset_title',
+ ],
+ 'edit' => [
+ 'label' => 'themeadmin_edit_title',
+ ],
],
],
- ],
- 'modsettings' => [
- 'label' => 'admin_modifications',
- 'function' => __NAMESPACE__ . '\\Mods::call',
- 'icon' => 'modifications',
- 'subsections' => [
- // MOD AUTHORS: If your mod has just a few simple
- // settings and doesn't need its own settings page, you
- // can use the integrate_general_mod_settings hook to
- // add them to the 'general' page.
- 'general' => [
- 'label' => 'mods_cat_modifications_misc',
+ 'modsettings' => [
+ 'label' => 'admin_modifications',
+ 'function' => __NAMESPACE__ . '\\Mods::call',
+ 'icon' => 'modifications',
+ 'subsections' => [
+ // MOD AUTHORS: If your mod has just a few simple
+ // settings and doesn't need its own settings page, you
+ // can use the integrate_general_mod_settings hook to
+ // add them to the 'general' page.
+ 'general' => [
+ 'label' => 'mods_cat_modifications_misc',
+ ],
+ // MOD AUTHORS: If your mod has a custom settings page,
+ // use the integrate_admin_areas hook to insert it here.
],
- // MOD AUTHORS: If your mod has a custom settings page,
- // use the integrate_admin_areas hook to insert it here.
],
],
],
- ],
- 'layout' => [
- 'title' => 'layout_controls',
- 'permission' => ['manage_boards', 'admin_forum', 'manage_smileys', 'manage_attachments', 'moderate_forum'],
- 'areas' => [
- 'manageboards' => [
- 'label' => 'admin_boards',
- 'function' => __NAMESPACE__ . '\\Boards::call',
- 'icon' => 'boards',
- 'permission' => ['manage_boards'],
- 'subsections' => [
- 'main' => [
- 'label' => 'boards_edit',
- ],
- 'newcat' => [
- 'label' => 'mboards_new_cat',
- ],
- 'settings' => [
- 'label' => 'settings',
- 'admin_forum',
+ 'layout' => [
+ 'title' => 'layout_controls',
+ 'permission' => ['manage_boards', 'admin_forum', 'manage_smileys', 'manage_attachments', 'moderate_forum'],
+ 'areas' => [
+ 'manageboards' => [
+ 'label' => 'admin_boards',
+ 'function' => __NAMESPACE__ . '\\Boards::call',
+ 'icon' => 'boards',
+ 'permission' => ['manage_boards'],
+ 'subsections' => [
+ 'main' => [
+ 'label' => 'boards_edit',
+ ],
+ 'newcat' => [
+ 'label' => 'mboards_new_cat',
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ 'admin_forum',
+ ],
],
],
- ],
- 'postsettings' => [
- 'label' => 'manageposts',
- 'function' => __NAMESPACE__ . '\\Posts::call',
- 'permission' => ['admin_forum'],
- 'icon' => 'posts',
- 'subsections' => [
- 'posts' => [
- 'label' => 'manageposts_settings',
- ],
- 'censor' => [
- 'label' => 'admin_censored_words',
- ],
- 'topics' => [
- 'label' => 'manageposts_topic_settings',
- ],
- 'drafts' => [
- 'label' => 'manage_drafts',
+ 'postsettings' => [
+ 'label' => 'manageposts',
+ 'function' => __NAMESPACE__ . '\\Posts::call',
+ 'permission' => ['admin_forum'],
+ 'icon' => 'posts',
+ 'subsections' => [
+ 'posts' => [
+ 'label' => 'manageposts_settings',
+ ],
+ 'censor' => [
+ 'label' => 'admin_censored_words',
+ ],
+ 'topics' => [
+ 'label' => 'manageposts_topic_settings',
+ ],
+ 'drafts' => [
+ 'label' => 'manage_drafts',
+ ],
],
],
- ],
- 'managecalendar' => [
- 'label' => 'manage_calendar',
- 'function' => __NAMESPACE__ . '\\Calendar::call',
- 'icon' => 'calendar',
- 'permission' => ['admin_forum'],
- 'inactive' => false,
- 'subsections' => [
- 'holidays' => [
- 'label' => 'manage_holidays',
- 'permission' => 'admin_forum',
- ],
- 'import' => [
- 'label' => 'calendar_import',
- 'permission' => 'admin_forum',
- ],
- 'settings' => [
- 'label' => 'calendar_settings',
- 'permission' => 'admin_forum',
+ 'managecalendar' => [
+ 'label' => 'manage_calendar',
+ 'function' => __NAMESPACE__ . '\\Calendar::call',
+ 'icon' => 'calendar',
+ 'permission' => ['admin_forum'],
+ 'inactive' => false,
+ 'subsections' => [
+ 'holidays' => [
+ 'label' => 'manage_holidays',
+ 'permission' => 'admin_forum',
+ ],
+ 'import' => [
+ 'label' => 'calendar_import',
+ 'permission' => 'admin_forum',
+ ],
+ 'settings' => [
+ 'label' => 'calendar_settings',
+ 'permission' => 'admin_forum',
+ ],
],
],
- ],
- 'managesearch' => [
- 'label' => 'manage_search',
- 'function' => __NAMESPACE__ . '\\Search::call',
- 'icon' => 'search',
- 'permission' => ['admin_forum'],
- 'subsections' => [
- 'weights' => [
- 'label' => 'search_weights',
- ],
- 'method' => [
- 'label' => 'search_method',
- ],
- 'settings' => [
- 'label' => 'settings',
+ 'managesearch' => [
+ 'label' => 'manage_search',
+ 'function' => __NAMESPACE__ . '\\Search::call',
+ 'icon' => 'search',
+ 'permission' => ['admin_forum'],
+ 'subsections' => [
+ 'weights' => [
+ 'label' => 'search_weights',
+ ],
+ 'method' => [
+ 'label' => 'search_method',
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ ],
],
],
- ],
- 'smileys' => [
- 'label' => 'smileys_manage',
- 'function' => __NAMESPACE__ . '\\Smileys::call',
- 'icon' => 'smiley',
- 'permission' => ['manage_smileys'],
- 'subsections' => [
- 'editsets' => [
- 'label' => 'smiley_sets',
- ],
- 'addsmiley' => [
- 'label' => 'smileys_add',
- 'enabled' => true,
- ],
- 'editsmileys' => [
- 'label' => 'smileys_edit',
- 'enabled' => true,
- ],
- 'setorder' => [
- 'label' => 'smileys_set_order',
- 'enabled' => true,
- ],
- 'editicons' => [
- 'label' => 'icons_edit_message_icons',
- 'enabled' => true,
- ],
- 'settings' => [
- 'label' => 'settings',
+ 'smileys' => [
+ 'label' => 'smileys_manage',
+ 'function' => __NAMESPACE__ . '\\Smileys::call',
+ 'icon' => 'smiley',
+ 'permission' => ['manage_smileys'],
+ 'subsections' => [
+ 'editsets' => [
+ 'label' => 'smiley_sets',
+ ],
+ 'addsmiley' => [
+ 'label' => 'smileys_add',
+ 'enabled' => true,
+ ],
+ 'editsmileys' => [
+ 'label' => 'smileys_edit',
+ 'enabled' => true,
+ ],
+ 'setorder' => [
+ 'label' => 'smileys_set_order',
+ 'enabled' => true,
+ ],
+ 'editicons' => [
+ 'label' => 'icons_edit_message_icons',
+ 'enabled' => true,
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ ],
],
],
- ],
- 'manageattachments' => [
- 'label' => 'attachments_avatars',
- 'function' => __NAMESPACE__ . '\\Attachments::call',
- 'icon' => 'attachment',
- 'permission' => ['manage_attachments'],
- 'subsections' => [
- 'browse' => [
- 'label' => 'attachment_manager_browse',
- ],
- 'attachments' => [
- 'label' => 'attachment_manager_settings',
- ],
- 'avatars' => [
- 'label' => 'attachment_manager_avatar_settings',
- ],
- 'attachpaths' => [
- 'label' => 'attach_directories',
- ],
- 'maintenance' => [
- 'label' => 'attachment_manager_maintenance',
+ 'managereactions' => [
+ 'label' => 'reactions_manage',
+ 'function' => __NAMESPACE__ . '\\Reactions::call',
+ 'icon' => 'like',
+ 'permission' => ['admin_forum'],
+ 'subsections' => [
+ 'settings' => [
+ 'label' => 'settings',
+ ],
+ 'edit' => [
+ 'label' => 'reactions_manage',
+ ],
],
],
- ],
- 'sengines' => [
- 'label' => 'search_engines',
- 'inactive' => false,
- 'function' => __NAMESPACE__ . '\\SearchEngines::call',
- 'icon' => 'engines',
- 'permission' => 'admin_forum',
- 'subsections' => [
- 'stats' => [
- 'label' => 'spider_stats',
- ],
- 'logs' => [
- 'label' => 'spider_logs',
- ],
- 'spiders' => [
- 'label' => 'spiders',
+ 'manageattachments' => [
+ 'label' => 'attachments_avatars',
+ 'function' => __NAMESPACE__ . '\\Attachments::call',
+ 'icon' => 'attachment',
+ 'permission' => ['manage_attachments'],
+ 'subsections' => [
+ 'browse' => [
+ 'label' => 'attachment_manager_browse',
+ ],
+ 'attachments' => [
+ 'label' => 'attachment_manager_settings',
+ ],
+ 'avatars' => [
+ 'label' => 'attachment_manager_avatar_settings',
+ ],
+ 'attachpaths' => [
+ 'label' => 'attach_directories',
+ ],
+ 'maintenance' => [
+ 'label' => 'attachment_manager_maintenance',
+ ],
],
- 'settings' => [
- 'label' => 'settings',
+ ],
+ 'sengines' => [
+ 'label' => 'search_engines',
+ 'inactive' => false,
+ 'function' => __NAMESPACE__ . '\\SearchEngines::call',
+ 'icon' => 'engines',
+ 'permission' => 'admin_forum',
+ 'subsections' => [
+ 'stats' => [
+ 'label' => 'spider_stats',
+ ],
+ 'logs' => [
+ 'label' => 'spider_logs',
+ ],
+ 'spiders' => [
+ 'label' => 'spiders',
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ ],
],
],
],
],
- ],
- 'members' => [
- 'title' => 'admin_manage_members',
- 'permission' => [
- 'moderate_forum',
- 'manage_membergroups',
- 'manage_bans',
- 'manage_permissions',
- 'admin_forum',
- ],
- 'areas' => [
- 'viewmembers' => [
- 'label' => 'admin_users',
- 'function' => __NAMESPACE__ . '\\Members::call',
- 'icon' => 'members',
- 'permission' => ['moderate_forum'],
- 'subsections' => [
- 'all' => [
- 'label' => 'view_all_members',
- ],
- 'search' => [
- 'label' => 'mlist_search',
- ],
- ],
+ 'members' => [
+ 'title' => 'admin_manage_members',
+ 'permission' => [
+ 'moderate_forum',
+ 'manage_membergroups',
+ 'manage_bans',
+ 'manage_permissions',
+ 'admin_forum',
],
- 'membergroups' => [
- 'label' => 'admin_groups',
- 'function' => __NAMESPACE__ . '\\Membergroups::call',
- 'icon' => 'membergroups',
- 'permission' => ['manage_membergroups'],
- 'subsections' => [
- 'index' => [
- 'label' => 'membergroups_edit_groups',
- 'permission' => 'manage_membergroups',
- ],
- 'add' => [
- 'label' => 'membergroups_new_group',
- 'permission' => 'manage_membergroups',
- ],
- 'settings' => [
- 'label' => 'settings',
- 'permission' => 'admin_forum',
+ 'areas' => [
+ 'viewmembers' => [
+ 'label' => 'admin_users',
+ 'function' => __NAMESPACE__ . '\\Members::call',
+ 'icon' => 'members',
+ 'permission' => ['moderate_forum'],
+ 'subsections' => [
+ 'all' => [
+ 'label' => 'view_all_members',
+ ],
+ 'search' => [
+ 'label' => 'mlist_search',
+ ],
],
],
- ],
- 'permissions' => [
- 'label' => 'edit_permissions',
- 'function' => __NAMESPACE__ . '\\Permissions::call',
- 'icon' => 'permissions',
- 'permission' => ['manage_permissions'],
- 'subsections' => [
- 'index' => [
- 'label' => 'permissions_groups',
- 'permission' => 'manage_permissions',
- ],
- 'board' => [
- 'label' => 'permissions_boards',
- 'permission' => 'manage_permissions',
- ],
- 'profiles' => [
- 'label' => 'permissions_profiles',
- 'permission' => 'manage_permissions',
+ 'membergroups' => [
+ 'label' => 'admin_groups',
+ 'function' => __NAMESPACE__ . '\\Membergroups::call',
+ 'icon' => 'membergroups',
+ 'permission' => ['manage_membergroups'],
+ 'subsections' => [
+ 'index' => [
+ 'label' => 'membergroups_edit_groups',
+ 'permission' => 'manage_membergroups',
+ ],
+ 'add' => [
+ 'label' => 'membergroups_new_group',
+ 'permission' => 'manage_membergroups',
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ 'permission' => 'admin_forum',
+ ],
],
- 'postmod' => [
- 'label' => 'permissions_post_moderation',
- 'permission' => 'manage_permissions',
- ],
- 'settings' => [
- 'label' => 'settings',
- 'permission' => 'admin_forum',
- ],
- ],
- ],
- 'regcenter' => [
- 'label' => 'registration_center',
- 'function' => __NAMESPACE__ . '\\Registration::call',
- 'icon' => 'regcenter',
- 'permission' => [
- 'admin_forum',
- 'moderate_forum',
],
- 'subsections' => [
- 'register' => [
- 'label' => 'admin_browse_register_new',
- 'permission' => 'moderate_forum',
- ],
- 'agreement' => [
- 'label' => 'registration_agreement',
- 'permission' => 'admin_forum',
- ],
- 'policy' => [
- 'label' => 'privacy_policy',
- 'permission' => 'admin_forum',
- ],
- 'reservednames' => [
- 'label' => 'admin_reserved_set',
- 'permission' => 'admin_forum',
- ],
- 'settings' => [
- 'label' => 'settings',
- 'permission' => 'admin_forum',
+ 'permissions' => [
+ 'label' => 'edit_permissions',
+ 'function' => __NAMESPACE__ . '\\Permissions::call',
+ 'icon' => 'permissions',
+ 'permission' => ['manage_permissions'],
+ 'subsections' => [
+ 'index' => [
+ 'label' => 'permissions_groups',
+ 'permission' => 'manage_permissions',
+ ],
+ 'board' => [
+ 'label' => 'permissions_boards',
+ 'permission' => 'manage_permissions',
+ ],
+ 'profiles' => [
+ 'label' => 'permissions_profiles',
+ 'permission' => 'manage_permissions',
+ ],
+ 'postmod' => [
+ 'label' => 'permissions_post_moderation',
+ 'permission' => 'manage_permissions',
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ 'permission' => 'admin_forum',
+ ],
],
],
- ],
- 'warnings' => [
- 'label' => 'warnings',
- 'function' => __NAMESPACE__ . '\\Warnings::call',
- 'icon' => 'warning',
- 'inactive' => false,
- 'permission' => ['admin_forum'],
- ],
- 'ban' => [
- 'label' => 'ban_title',
- 'function' => __NAMESPACE__ . '\\Bans::call',
- 'icon' => 'ban',
- 'permission' => 'manage_bans',
- 'subsections' => [
- 'list' => [
- 'label' => 'ban_edit_list',
- ],
- 'add' => [
- 'label' => 'ban_add_new',
- ],
- 'browse' => [
- 'label' => 'ban_trigger_browse',
- ],
- 'log' => [
- 'label' => 'ban_log',
+ 'regcenter' => [
+ 'label' => 'registration_center',
+ 'function' => __NAMESPACE__ . '\\Registration::call',
+ 'icon' => 'regcenter',
+ 'permission' => [
+ 'admin_forum',
+ 'moderate_forum',
+ ],
+ 'subsections' => [
+ 'register' => [
+ 'label' => 'admin_browse_register_new',
+ 'permission' => 'moderate_forum',
+ ],
+ 'agreement' => [
+ 'label' => 'registration_agreement',
+ 'permission' => 'admin_forum',
+ ],
+ 'policy' => [
+ 'label' => 'privacy_policy',
+ 'permission' => 'admin_forum',
+ ],
+ 'reservednames' => [
+ 'label' => 'admin_reserved_set',
+ 'permission' => 'admin_forum',
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ 'permission' => 'admin_forum',
+ ],
],
],
- ],
- 'paidsubscribe' => [
- 'label' => 'paid_subscriptions',
- 'inactive' => false,
- 'function' => __NAMESPACE__ . '\\Subscriptions::call',
- 'icon' => 'paid',
- 'permission' => 'admin_forum',
- 'subsections' => [
- 'view' => [
- 'label' => 'paid_subs_view',
+ 'warnings' => [
+ 'label' => 'warnings',
+ 'function' => __NAMESPACE__ . '\\Warnings::call',
+ 'icon' => 'warning',
+ 'inactive' => false,
+ 'permission' => ['admin_forum'],
+ ],
+ 'ban' => [
+ 'label' => 'ban_title',
+ 'function' => __NAMESPACE__ . '\\Bans::call',
+ 'icon' => 'ban',
+ 'permission' => 'manage_bans',
+ 'subsections' => [
+ 'list' => [
+ 'label' => 'ban_edit_list',
+ ],
+ 'add' => [
+ 'label' => 'ban_add_new',
+ ],
+ 'browse' => [
+ 'label' => 'ban_trigger_browse',
+ ],
+ 'log' => [
+ 'label' => 'ban_log',
+ ],
],
- 'settings' => [
- 'label' => 'settings',
+ ],
+ 'paidsubscribe' => [
+ 'label' => 'paid_subscriptions',
+ 'inactive' => false,
+ 'function' => __NAMESPACE__ . '\\Subscriptions::call',
+ 'icon' => 'paid',
+ 'permission' => 'admin_forum',
+ 'subsections' => [
+ 'view' => [
+ 'label' => 'paid_subs_view',
+ ],
+ 'settings' => [
+ 'label' => 'settings',
+ ],
],
],
],
],
- ],
- 'maintenance' => [
- 'title' => 'admin_maintenance',
- 'permission' => ['admin_forum'],
- 'areas' => [
- 'serversettings' => [
- 'label' => 'admin_server_settings',
- 'function' => __NAMESPACE__ . '\\Server::call',
- 'icon' => 'server',
- 'subsections' => [
- 'general' => [
- 'label' => 'general_settings',
- ],
- 'database' => [
- 'label' => 'database_settings',
- ],
- 'cookie' => [
- 'label' => 'cookies_sessions_settings',
- ],
- 'security' => [
- 'label' => 'security_settings',
- ],
- 'cache' => [
- 'label' => 'caching_settings',
- ],
- 'export' => [
- 'label' => 'export_settings',
- ],
- 'loads' => [
- 'label' => 'load_balancing_settings',
- ],
- 'phpinfo' => [
- 'label' => 'phpinfo_settings',
+ 'maintenance' => [
+ 'title' => 'admin_maintenance',
+ 'permission' => ['admin_forum'],
+ 'areas' => [
+ 'serversettings' => [
+ 'label' => 'admin_server_settings',
+ 'function' => __NAMESPACE__ . '\\Server::call',
+ 'icon' => 'server',
+ 'subsections' => [
+ 'general' => [
+ 'label' => 'general_settings',
+ ],
+ 'database' => [
+ 'label' => 'database_settings',
+ ],
+ 'cookie' => [
+ 'label' => 'cookies_sessions_settings',
+ ],
+ 'security' => [
+ 'label' => 'security_settings',
+ ],
+ 'cache' => [
+ 'label' => 'caching_settings',
+ ],
+ 'export' => [
+ 'label' => 'export_settings',
+ ],
+ 'loads' => [
+ 'label' => 'load_balancing_settings',
+ ],
+ 'phpinfo' => [
+ 'label' => 'phpinfo_settings',
+ ],
],
],
- ],
- 'maintain' => [
- 'label' => 'maintain_title',
- 'function' => __NAMESPACE__ . '\\Maintenance::call',
- 'icon' => 'maintain',
- 'subsections' => [
- 'routine' => [
- 'label' => 'maintain_sub_routine',
- 'permission' => 'admin_forum',
- ],
- 'database' => [
- 'label' => 'maintain_sub_database',
- 'permission' => 'admin_forum',
- ],
- 'members' => [
- 'label' => 'maintain_sub_members',
- 'permission' => 'admin_forum',
- ],
- 'topics' => [
- 'label' => 'maintain_sub_topics',
- 'permission' => 'admin_forum',
- ],
- 'hooks' => [
- 'label' => 'hooks_title_list',
- 'permission' => 'admin_forum',
+ 'maintain' => [
+ 'label' => 'maintain_title',
+ 'function' => __NAMESPACE__ . '\\Maintenance::call',
+ 'icon' => 'maintain',
+ 'subsections' => [
+ 'routine' => [
+ 'label' => 'maintain_sub_routine',
+ 'permission' => 'admin_forum',
+ ],
+ 'database' => [
+ 'label' => 'maintain_sub_database',
+ 'permission' => 'admin_forum',
+ ],
+ 'members' => [
+ 'label' => 'maintain_sub_members',
+ 'permission' => 'admin_forum',
+ ],
+ 'topics' => [
+ 'label' => 'maintain_sub_topics',
+ 'permission' => 'admin_forum',
+ ],
+ 'hooks' => [
+ 'label' => 'hooks_title_list',
+ 'permission' => 'admin_forum',
+ ],
],
],
- ],
- 'scheduledtasks' => [
- 'label' => 'maintain_tasks',
- 'function' => __NAMESPACE__ . '\\Tasks::call',
- 'icon' => 'scheduled',
- 'subsections' => [
- 'tasks' => [
- 'label' => 'maintain_tasks',
- 'permission' => 'admin_forum',
- ],
- 'tasklog' => [
- 'label' => 'scheduled_log',
- 'permission' => 'admin_forum',
- ],
- 'settings' => [
- 'label' => 'scheduled_tasks_settings',
- 'permission' => 'admin_forum',
+ 'scheduledtasks' => [
+ 'label' => 'maintain_tasks',
+ 'function' => __NAMESPACE__ . '\\Tasks::call',
+ 'icon' => 'scheduled',
+ 'subsections' => [
+ 'tasks' => [
+ 'label' => 'maintain_tasks',
+ 'permission' => 'admin_forum',
+ ],
+ 'tasklog' => [
+ 'label' => 'scheduled_log',
+ 'permission' => 'admin_forum',
+ ],
+ 'settings' => [
+ 'label' => 'scheduled_tasks_settings',
+ 'permission' => 'admin_forum',
+ ],
],
],
- ],
- 'mailqueue' => [
- 'label' => 'mailqueue_title',
- 'function' => __NAMESPACE__ . '\\Mail::call',
- 'icon' => 'mail',
- 'subsections' => [
- 'browse' => [
- 'label' => 'mailqueue_browse',
- 'permission' => 'admin_forum',
- ],
- 'settings' => [
- 'label' => 'mailqueue_settings',
- 'permission' => 'admin_forum',
- ],
- 'test' => [
- 'label' => 'mailqueue_test',
- 'permission' => 'admin_forum',
+ 'mailqueue' => [
+ 'label' => 'mailqueue_title',
+ 'function' => __NAMESPACE__ . '\\Mail::call',
+ 'icon' => 'mail',
+ 'subsections' => [
+ 'browse' => [
+ 'label' => 'mailqueue_browse',
+ 'permission' => 'admin_forum',
+ ],
+ 'settings' => [
+ 'label' => 'mailqueue_settings',
+ 'permission' => 'admin_forum',
+ ],
+ 'test' => [
+ 'label' => 'mailqueue_test',
+ 'permission' => 'admin_forum',
+ ],
],
],
- ],
- 'reports' => [
- 'label' => 'generate_reports',
- 'function' => __NAMESPACE__ . '\\Reports::call',
- 'icon' => 'reports',
- ],
- 'logs' => [
- 'label' => 'logs',
- 'function' => __NAMESPACE__ . '\\Logs::call',
- 'icon' => 'logs',
- 'subsections' => [
- 'errorlog' => [
- 'label' => 'errorlog',
- 'permission' => 'admin_forum',
- 'enabled' => true,
- 'url' => '{scripturl}?action=admin;area=logs;sa=errorlog;desc',
- ],
- 'adminlog' => [
- 'label' => 'admin_log',
- 'permission' => 'admin_forum',
- 'enabled' => true,
- ],
- 'modlog' => [
- 'label' => 'moderation_log',
- 'permission' => 'admin_forum',
- 'enabled' => true,
- ],
- 'banlog' => [
- 'label' => 'ban_log',
- 'permission' => 'manage_bans',
- ],
- 'spiderlog' => [
- 'label' => 'spider_logs',
- 'permission' => 'admin_forum',
- 'enabled' => true,
- ],
- 'tasklog' => [
- 'label' => 'scheduled_log',
- 'permission' => 'admin_forum',
- ],
- 'settings' => [
- 'label' => 'log_settings',
- 'permission' => 'admin_forum',
+ 'reports' => [
+ 'label' => 'generate_reports',
+ 'function' => __NAMESPACE__ . '\\Reports::call',
+ 'icon' => 'reports',
+ ],
+ 'logs' => [
+ 'label' => 'logs',
+ 'function' => __NAMESPACE__ . '\\Logs::call',
+ 'icon' => 'logs',
+ 'subsections' => [
+ 'errorlog' => [
+ 'label' => 'errorlog',
+ 'permission' => 'admin_forum',
+ 'enabled' => true,
+ 'url' => '{scripturl}?action=admin;area=logs;sa=errorlog;desc',
+ ],
+ 'adminlog' => [
+ 'label' => 'admin_log',
+ 'permission' => 'admin_forum',
+ 'enabled' => true,
+ ],
+ 'modlog' => [
+ 'label' => 'moderation_log',
+ 'permission' => 'admin_forum',
+ 'enabled' => true,
+ ],
+ 'banlog' => [
+ 'label' => 'ban_log',
+ 'permission' => 'manage_bans',
+ ],
+ 'spiderlog' => [
+ 'label' => 'spider_logs',
+ 'permission' => 'admin_forum',
+ 'enabled' => true,
+ ],
+ 'tasklog' => [
+ 'label' => 'scheduled_log',
+ 'permission' => 'admin_forum',
+ ],
+ 'settings' => [
+ 'label' => 'log_settings',
+ 'permission' => 'admin_forum',
+ ],
],
],
- ],
- 'repairboards' => [
- 'label' => 'admin_repair',
- 'function' => __NAMESPACE__ . '\\RepairBoards::call',
- 'select' => 'maintain',
- 'hidden' => true,
+ 'repairboards' => [
+ 'label' => 'admin_repair',
+ 'function' => __NAMESPACE__ . '\\RepairBoards::call',
+ 'select' => 'maintain',
+ 'hidden' => true,
+ ],
],
],
- ],
- ];
-
- /****************
- * Public methods
- ****************/
-
- /**
- * The main admin handling function.
- *
- * It initialises all the basic context required for the admin center.
- * It passes execution onto the relevant admin section.
- * If the passed section is not found it shows the admin home page.
- */
- public function execute(): void
- {
- // Make sure the administrator has a valid session...
- User::$me->validateSession();
-
- // Actually create the menu!
- // Hook call disabled because we already called it in setAdminAreas()
- $menu = new Menu($this->admin_areas, [
- 'do_big_icons' => true,
- 'disable_hook_call' => true,
- ]);
-
- // Nothing valid?
- if (empty($menu->include_data)) {
- ErrorHandler::fatalLang('no_access', false);
- }
-
- // Build the link tree.
- Utils::$context['linktree'][] = [
- 'url' => Config::$scripturl . '?action=admin',
- 'name' => Lang::$txt['admin_center'],
];
- if (isset($menu->current_area) && $menu->current_area != 'index') {
- Utils::$context['linktree'][] = [
- 'url' => Config::$scripturl . '?action=admin;area=' . $menu->current_area . ';' . Utils::$context['session_var'] . '=' . Utils::$context['session_id'],
- 'name' => $menu->include_data['label'],
- ];
- }
+ /****************
+ * Public methods
+ ****************/
+
+ /**
+ * The main admin handling function.
+ *
+ * It initialises all the basic context required for the admin center.
+ * It passes execution onto the relevant admin section.
+ * If the passed section is not found it shows the admin home page.
+ */
+ public function execute(): void
+ {
+ // Make sure the administrator has a valid session...
+ User::$me->validateSession();
+
+ // Actually create the menu!
+ // Hook call disabled because we already called it in setAdminAreas()
+ $menu = new Menu($this->admin_areas, [
+ 'do_big_icons' => true,
+ 'disable_hook_call' => true,
+ ]);
+
+ // Nothing valid?
+ if (empty($menu->include_data)) {
+ ErrorHandler::fatalLang('no_access', false);
+ }
- if (!empty($menu->current_subsection) && $menu->include_data['subsections'][$menu->current_subsection]['label'] != $menu->include_data['label']) {
+ // Build the link tree.
Utils::$context['linktree'][] = [
- 'url' => Config::$scripturl . '?action=admin;area=' . $menu->current_area . ';sa=' . $menu->current_subsection . ';' . Utils::$context['session_var'] . '=' . Utils::$context['session_id'],
- 'name' => $menu->include_data['subsections'][$menu->current_subsection]['label'],
+ 'url' => Config::$scripturl . '?action=admin',
+ 'name' => Lang::$txt['admin_center'],
];
- }
- // Make a note of the Unique ID for this menu.
- Utils::$context['admin_menu_id'] = $menu->id;
- Utils::$context['admin_menu_name'] = $menu->name;
+ if (isset($menu->current_area) && $menu->current_area != 'index') {
+ Utils::$context['linktree'][] = [
+ 'url' => Config::$scripturl . '?action=admin;area=' . $menu->current_area . ';' . Utils::$context['session_var'] . '=' . Utils::$context['session_id'],
+ 'name' => $menu->include_data['label'],
+ ];
+ }
- // Where in the admin are we?
- Utils::$context['admin_area'] = $menu->current_area;
+ if (!empty($menu->current_subsection) && $menu->include_data['subsections'][$menu->current_subsection]['label'] != $menu->include_data['label']) {
+ Utils::$context['linktree'][] = [
+ 'url' => Config::$scripturl . '?action=admin;area=' . $menu->current_area . ';sa=' . $menu->current_subsection . ';' . Utils::$context['session_var'] . '=' . Utils::$context['session_id'],
+ 'name' => $menu->include_data['subsections'][$menu->current_subsection]['label'],
+ ];
+ }
- // Now - finally - call the right place!
- if (isset($menu->include_data['file'])) {
- require_once Config::$sourcedir . '/' . $menu->include_data['file'];
- }
+ // Make a note of the Unique ID for this menu.
+ Utils::$context['admin_menu_id'] = $menu->id;
+ Utils::$context['admin_menu_name'] = $menu->name;
- // Get the right callable.
- $call = Utils::getCallable($menu->include_data['function']);
+ // Where in the admin are we?
+ Utils::$context['admin_area'] = $menu->current_area;
- // Is it valid?
- if (!empty($call)) {
- call_user_func($call);
- }
- }
+ // Now - finally - call the right place!
+ if (isset($menu->include_data['file'])) {
+ require_once Config::$sourcedir . '/' . $menu->include_data['file'];
+ }
- /***********************
- * Public static methods
- ***********************/
+ // Get the right callable.
+ $call = Utils::getCallable($menu->include_data['function']);
- /**
- * Helper function, it sets up the context for database settings.
- *
- * @todo see rev. 10406 from 2.1-requests
- *
- * @param array $config_vars An array of configuration variables
- */
- public static function prepareDBSettingContext(array &$config_vars): void
- {
- Lang::load('Help');
-
- if (isset($_SESSION['adm-save'])) {
- if ($_SESSION['adm-save'] === true) {
- Utils::$context['saved_successful'] = true;
- } else {
- Utils::$context['saved_failed'] = $_SESSION['adm-save'];
+ // Is it valid?
+ if (!empty($call)) {
+ call_user_func($call);
}
-
- unset($_SESSION['adm-save']);
}
- Utils::$context['config_vars'] = [];
- $inlinePermissions = [];
- $bbcChoice = [];
- $board_list = false;
-
- foreach ($config_vars as $config_var) {
- // HR?
- if (!is_array($config_var)) {
- Utils::$context['config_vars'][] = $config_var;
- } else {
- // If it has no name it doesn't have any purpose!
- if (empty($config_var[1])) {
- continue;
- }
-
- // Special case for inline permissions
- if ($config_var[0] == 'permissions' && User::$me->allowedTo('manage_permissions')) {
- $inlinePermissions[] = $config_var[1];
- } elseif ($config_var[0] == 'permissions') {
- continue;
- }
-
- if ($config_var[0] == 'boards') {
- $board_list = true;
+ /***********************
+ * Public static methods
+ ***********************/
+
+ /**
+ * Helper function, it sets up the context for database settings.
+ *
+ * @todo see rev. 10406 from 2.1-requests
+ *
+ * @param array $config_vars An array of configuration variables
+ */
+ public static function prepareDBSettingContext(array &$config_vars): void
+ {
+ Lang::load('Help');
+
+ if (isset($_SESSION['adm-save'])) {
+ if ($_SESSION['adm-save'] === true) {
+ Utils::$context['saved_successful'] = true;
+ } else {
+ Utils::$context['saved_failed'] = $_SESSION['adm-save'];
}
- // Are we showing the BBC selection box?
- if ($config_var[0] == 'bbc') {
- $bbcChoice[] = $config_var[1];
- }
+ unset($_SESSION['adm-save']);
+ }
- // We need to do some parsing of the value before we pass it in.
- if (isset(Config::$modSettings[$config_var[1]])) {
- switch ($config_var[0]) {
- case 'select':
- $value = Config::$modSettings[$config_var[1]];
- break;
+ Utils::$context['config_vars'] = [];
+ $inlinePermissions = [];
+ $bbcChoice = [];
+ $board_list = false;
- case 'json':
- $value = Utils::htmlspecialchars(Utils::jsonEncode(Config::$modSettings[$config_var[1]]));
- break;
+ foreach ($config_vars as $config_var) {
+ // HR?
+ if (!is_array($config_var)) {
+ Utils::$context['config_vars'][] = $config_var;
+ } else {
+ // If it has no name it doesn't have any purpose!
+ if (empty($config_var[1])) {
+ continue;
+ }
- case 'boards':
- $value = explode(',', Config::$modSettings[$config_var[1]]);
- break;
+ // Special case for inline permissions
+ if ($config_var[0] == 'permissions' && User::$me->allowedTo('manage_permissions')) {
+ $inlinePermissions[] = $config_var[1];
+ } elseif ($config_var[0] == 'permissions') {
+ continue;
+ }
- default:
- $value = Utils::htmlspecialchars((string) Config::$modSettings[$config_var[1]]);
+ if ($config_var[0] == 'boards') {
+ $board_list = true;
}
- } else {
- // Darn, it's empty. What type is expected?
- switch ($config_var[0]) {
- case 'int':
- case 'float':
- $value = 0;
- break;
- case 'select':
- $value = !empty($config_var['multiple']) ? Utils::jsonEncode([]) : '';
- break;
+ // Are we showing the BBC selection box?
+ if ($config_var[0] == 'bbc') {
+ $bbcChoice[] = $config_var[1];
+ }
- case 'boards':
- $value = [];
- break;
+ // We need to do some parsing of the value before we pass it in.
+ if (isset(Config::$modSettings[$config_var[1]])) {
+ switch ($config_var[0]) {
+ case 'select':
+ $value = Config::$modSettings[$config_var[1]];
+ break;
- default:
- $value = '';
- }
- }
+ case 'json':
+ $value = Utils::htmlspecialchars(Utils::jsonEncode(Config::$modSettings[$config_var[1]]));
+ break;
- Utils::$context['config_vars'][$config_var[1]] = [
- 'label' => $config_var['text_label'] ?? (Lang::$txt[$config_var[1]] ?? (isset($config_var[3]) && !is_array($config_var[3]) ? $config_var[3] : '')),
- 'help' => isset(Lang::$helptxt[$config_var[1]]) ? $config_var[1] : '',
- 'type' => $config_var[0],
- 'size' => !empty($config_var['size']) ? $config_var['size'] : (!empty($config_var[2]) && !is_array($config_var[2]) ? $config_var[2] : (in_array($config_var[0], ['int', 'float']) ? 6 : 0)),
- 'data' => [],
- 'name' => $config_var[1],
- 'value' => $value,
- 'disabled' => false,
- 'invalid' => !empty($config_var['invalid']),
- 'javascript' => '',
- 'var_message' => !empty($config_var['message']) && isset(Lang::$txt[$config_var['message']]) ? Lang::$txt[$config_var['message']] : '',
- 'preinput' => $config_var['preinput'] ?? '',
- 'postinput' => $config_var['postinput'] ?? '',
- ];
+ case 'boards':
+ $value = explode(',', Config::$modSettings[$config_var[1]]);
+ break;
- // Handle min/max/step if necessary
- if ($config_var[0] == 'int' || $config_var[0] == 'float') {
- // Default to a min of 0 if one isn't set
- if (isset($config_var['min'])) {
- Utils::$context['config_vars'][$config_var[1]]['min'] = $config_var['min'];
+ default:
+ $value = Utils::htmlspecialchars((string) Config::$modSettings[$config_var[1]]);
+ }
} else {
- Utils::$context['config_vars'][$config_var[1]]['min'] = 0;
- }
+ // Darn, it's empty. What type is expected?
+ switch ($config_var[0]) {
+ case 'int':
+ case 'float':
+ $value = 0;
+ break;
- if (isset($config_var['max'])) {
- Utils::$context['config_vars'][$config_var[1]]['max'] = $config_var['max'];
- }
+ case 'select':
+ $value = !empty($config_var['multiple']) ? Utils::jsonEncode([]) : '';
+ break;
+
+ case 'boards':
+ $value = [];
+ break;
- if (isset($config_var['step'])) {
- Utils::$context['config_vars'][$config_var[1]]['step'] = $config_var['step'];
+ default:
+ $value = '';
+ }
}
- }
- // If this is a select box handle any data.
- if (!empty($config_var[2]) && is_array($config_var[2])) {
- // If we allow multiple selections, we need to adjust a few things.
- if ($config_var[0] == 'select' && !empty($config_var['multiple'])) {
- Utils::$context['config_vars'][$config_var[1]]['name'] .= '[]';
+ Utils::$context['config_vars'][$config_var[1]] = [
+ 'label' => $config_var['text_label'] ?? (Lang::$txt[$config_var[1]] ?? (isset($config_var[3]) && !is_array($config_var[3]) ? $config_var[3] : '')),
+ 'help' => isset(Lang::$helptxt[$config_var[1]]) ? $config_var[1] : '',
+ 'type' => $config_var[0],
+ 'size' => !empty($config_var['size']) ? $config_var['size'] : (!empty($config_var[2]) && !is_array($config_var[2]) ? $config_var[2] : (in_array($config_var[0], ['int', 'float']) ? 6 : 0)),
+ 'data' => [],
+ 'name' => $config_var[1],
+ 'value' => $value,
+ 'disabled' => false,
+ 'invalid' => !empty($config_var['invalid']),
+ 'javascript' => '',
+ 'var_message' => !empty($config_var['message']) && isset(Lang::$txt[$config_var['message']]) ? Lang::$txt[$config_var['message']] : '',
+ 'preinput' => $config_var['preinput'] ?? '',
+ 'postinput' => $config_var['postinput'] ?? '',
+ ];
- Utils::$context['config_vars'][$config_var[1]]['value'] = !empty(Utils::$context['config_vars'][$config_var[1]]['value']) ? Utils::jsonDecode(Utils::$context['config_vars'][$config_var[1]]['value'], true) : [];
- }
+ // Handle min/max/step if necessary
+ if ($config_var[0] == 'int' || $config_var[0] == 'float') {
+ // Default to a min of 0 if one isn't set
+ if (isset($config_var['min'])) {
+ Utils::$context['config_vars'][$config_var[1]]['min'] = $config_var['min'];
+ } else {
+ Utils::$context['config_vars'][$config_var[1]]['min'] = 0;
+ }
- // If it's associative
- if (isset($config_var[2][0]) && is_array($config_var[2][0])) {
- Utils::$context['config_vars'][$config_var[1]]['data'] = $config_var[2];
- } else {
- foreach ($config_var[2] as $key => $item) {
- Utils::$context['config_vars'][$config_var[1]]['data'][] = [$key, $item];
+ if (isset($config_var['max'])) {
+ Utils::$context['config_vars'][$config_var[1]]['max'] = $config_var['max'];
}
- }
- if (empty($config_var['size']) && !empty($config_var['multiple'])) {
- Utils::$context['config_vars'][$config_var[1]]['size'] = max(4, count($config_var[2]));
+ if (isset($config_var['step'])) {
+ Utils::$context['config_vars'][$config_var[1]]['step'] = $config_var['step'];
+ }
}
- }
- // Finally allow overrides - and some final cleanups.
- foreach ($config_var as $k => $v) {
- if (!is_numeric($k)) {
- if (str_starts_with($k, 'on')) {
- Utils::$context['config_vars'][$config_var[1]]['javascript'] .= ' ' . $k . '="' . $v . '"';
+ // If this is a select box handle any data.
+ if (!empty($config_var[2]) && is_array($config_var[2])) {
+ // If we allow multiple selections, we need to adjust a few things.
+ if ($config_var[0] == 'select' && !empty($config_var['multiple'])) {
+ Utils::$context['config_vars'][$config_var[1]]['name'] .= '[]';
+
+ Utils::$context['config_vars'][$config_var[1]]['value'] = !empty(Utils::$context['config_vars'][$config_var[1]]['value']) ? Utils::jsonDecode(Utils::$context['config_vars'][$config_var[1]]['value'], true) : [];
+ }
+
+ // If it's associative
+ if (isset($config_var[2][0]) && is_array($config_var[2][0])) {
+ Utils::$context['config_vars'][$config_var[1]]['data'] = $config_var[2];
} else {
- Utils::$context['config_vars'][$config_var[1]][$k] = $v;
+ foreach ($config_var[2] as $key => $item) {
+ Utils::$context['config_vars'][$config_var[1]]['data'][] = [$key, $item];
+ }
+ }
+
+ if (empty($config_var['size']) && !empty($config_var['multiple'])) {
+ Utils::$context['config_vars'][$config_var[1]]['size'] = max(4, count($config_var[2]));
}
}
- // See if there are any other labels that might fit?
- if (isset(Lang::$txt['setting_' . $config_var[1]])) {
- Utils::$context['config_vars'][$config_var[1]]['label'] = Lang::$txt['setting_' . $config_var[1]];
- } elseif (isset(Lang::$txt['groups_' . $config_var[1]])) {
- Utils::$context['config_vars'][$config_var[1]]['label'] = Lang::$txt['groups_' . $config_var[1]];
+ // Finally allow overrides - and some final cleanups.
+ foreach ($config_var as $k => $v) {
+ if (!is_numeric($k)) {
+ if (str_starts_with($k, 'on')) {
+ Utils::$context['config_vars'][$config_var[1]]['javascript'] .= ' ' . $k . '="' . $v . '"';
+ } else {
+ Utils::$context['config_vars'][$config_var[1]][$k] = $v;
+ }
+ }
+
+ // See if there are any other labels that might fit?
+ if (isset(Lang::$txt['setting_' . $config_var[1]])) {
+ Utils::$context['config_vars'][$config_var[1]]['label'] = Lang::$txt['setting_' . $config_var[1]];
+ } elseif (isset(Lang::$txt['groups_' . $config_var[1]])) {
+ Utils::$context['config_vars'][$config_var[1]]['label'] = Lang::$txt['groups_' . $config_var[1]];
+ }
}
- }
- // Set the subtext in case it's part of the label.
- // @todo Temporary. Preventing divs inside label tags.
- $divPos = strpos(Utils::$context['config_vars'][$config_var[1]]['label'], '
]*>~', '', substr(Utils::$context['config_vars'][$config_var[1]]['label'], $divPos));
+ if ($divPos !== false) {
+ Utils::$context['config_vars'][$config_var[1]]['subtext'] = preg_replace('~?div[^>]*>~', '', substr(Utils::$context['config_vars'][$config_var[1]]['label'], $divPos));
- Utils::$context['config_vars'][$config_var[1]]['label'] = substr(Utils::$context['config_vars'][$config_var[1]]['label'], 0, $divPos);
+ Utils::$context['config_vars'][$config_var[1]]['label'] = substr(Utils::$context['config_vars'][$config_var[1]]['label'], 0, $divPos);
+ }
}
}
- }
- // If we have inline permissions we need to prep them.
- if (!empty($inlinePermissions) && User::$me->allowedTo('manage_permissions')) {
- Permissions::init_inline_permissions($inlinePermissions);
- }
+ // If we have inline permissions we need to prep them.
+ if (!empty($inlinePermissions) && User::$me->allowedTo('manage_permissions')) {
+ Permissions::init_inline_permissions($inlinePermissions);
+ }
- if ($board_list) {
- Utils::$context['board_list'] = MessageIndex::getBoardList();
- }
+ if ($board_list) {
+ Utils::$context['board_list'] = MessageIndex::getBoardList();
+ }
- // What about any BBC selection boxes?
- if (!empty($bbcChoice)) {
- // What are the options, eh?
- $temp = BBCodeParser::getCodes();
- $bbcTags = [];
+ // What about any BBC selection boxes?
+ if (!empty($bbcChoice)) {
+ // What are the options, eh?
+ $temp = BBCodeParser::getCodes();
+ $bbcTags = [];
- foreach ($temp as $tag) {
- if (!isset($tag['require_parents'])) {
- $bbcTags[] = $tag['tag'];
+ foreach ($temp as $tag) {
+ if (!isset($tag['require_parents'])) {
+ $bbcTags[] = $tag['tag'];
+ }
}
- }
- $bbcTags = array_unique($bbcTags);
+ $bbcTags = array_unique($bbcTags);
- // The number of columns we want to show the BBC tags in.
- $numColumns = Utils::$context['num_bbc_columns'] ?? 3;
+ // The number of columns we want to show the BBC tags in.
+ $numColumns = Utils::$context['num_bbc_columns'] ?? 3;
- // Now put whatever BBC options we may have into context too!
- Utils::$context['bbc_sections'] = [];
+ // Now put whatever BBC options we may have into context too!
+ Utils::$context['bbc_sections'] = [];
- foreach ($bbcChoice as $bbcSection) {
- Utils::$context['bbc_sections'][$bbcSection] = [
- 'title' => Lang::$txt['bbc_title_' . $bbcSection] ?? Lang::$txt['enabled_bbc_select'],
- 'disabled' => empty(Config::$modSettings['bbc_disabled_' . $bbcSection]) ? [] : Config::$modSettings['bbc_disabled_' . $bbcSection],
- 'all_selected' => empty(Config::$modSettings['bbc_disabled_' . $bbcSection]),
- 'columns' => [],
- ];
+ foreach ($bbcChoice as $bbcSection) {
+ Utils::$context['bbc_sections'][$bbcSection] = [
+ 'title' => Lang::$txt['bbc_title_' . $bbcSection] ?? Lang::$txt['enabled_bbc_select'],
+ 'disabled' => empty(Config::$modSettings['bbc_disabled_' . $bbcSection]) ? [] : Config::$modSettings['bbc_disabled_' . $bbcSection],
+ 'all_selected' => empty(Config::$modSettings['bbc_disabled_' . $bbcSection]),
+ 'columns' => [],
+ ];
- if ($bbcSection == 'legacyBBC') {
- $sectionTags = array_intersect(Utils::$context['legacy_bbc'], $bbcTags);
- } else {
- $sectionTags = array_diff($bbcTags, Utils::$context['legacy_bbc']);
- }
+ if ($bbcSection == 'legacyBBC') {
+ $sectionTags = array_intersect(Utils::$context['legacy_bbc'], $bbcTags);
+ } else {
+ $sectionTags = array_diff($bbcTags, Utils::$context['legacy_bbc']);
+ }
- $totalTags = count($sectionTags);
- $tagsPerColumn = ceil($totalTags / $numColumns);
+ $totalTags = count($sectionTags);
+ $tagsPerColumn = ceil($totalTags / $numColumns);
- $col = 0;
- $i = 0;
+ $col = 0;
+ $i = 0;
- foreach ($sectionTags as $tag) {
- if ($i % $tagsPerColumn == 0 && $i != 0) {
- $col++;
- }
+ foreach ($sectionTags as $tag) {
+ if ($i % $tagsPerColumn == 0 && $i != 0) {
+ $col++;
+ }
- Utils::$context['bbc_sections'][$bbcSection]['columns'][$col][] = [
- 'tag' => $tag,
- 'show_help' => isset(Lang::$helptxt['tag_' . $tag]),
- ];
+ Utils::$context['bbc_sections'][$bbcSection]['columns'][$col][] = [
+ 'tag' => $tag,
+ 'show_help' => isset(Lang::$helptxt['tag_' . $tag]),
+ ];
- $i++;
+ $i++;
+ }
}
}
+
+ IntegrationHook::call('integrate_prepare_db_settings', [&$config_vars]);
+ SecurityToken::create('admin-dbsc');
}
- IntegrationHook::call('integrate_prepare_db_settings', [&$config_vars]);
- SecurityToken::create('admin-dbsc');
- }
+ /**
+ * Helper function. Saves settings by putting them in Settings.php or saving them in the settings table.
+ *
+ * - Saves those settings set from ?action=admin;area=serversettings.
+ * - Requires the admin_forum permission.
+ * - Contains arrays of the types of data to save into Settings.php.
+ *
+ * @param array $config_vars An array of configuration variables
+ */
+ public static function saveSettings(array &$config_vars): void
+ {
+ SecurityToken::validate('admin-ssc');
+
+ // Fix the darn stupid cookiename! (more may not be allowed, but these for sure!)
+ if (isset($_POST['cookiename'])) {
+ $_POST['cookiename'] = preg_replace('~[,;\s\.$]+~' . (Utils::$context['utf8'] ? 'u' : ''), '', $_POST['cookiename']);
+ }
- /**
- * Helper function. Saves settings by putting them in Settings.php or saving them in the settings table.
- *
- * - Saves those settings set from ?action=admin;area=serversettings.
- * - Requires the admin_forum permission.
- * - Contains arrays of the types of data to save into Settings.php.
- *
- * @param array $config_vars An array of configuration variables
- */
- public static function saveSettings(array &$config_vars): void
- {
- SecurityToken::validate('admin-ssc');
+ // Fix the forum's URL if necessary.
+ if (isset($_POST['boardurl'])) {
+ if (str_ends_with($_POST['boardurl'], '/index.php')) {
+ $_POST['boardurl'] = substr($_POST['boardurl'], 0, -10);
+ } elseif (str_ends_with($_POST['boardurl'], '/')) {
+ $_POST['boardurl'] = substr($_POST['boardurl'], 0, -1);
+ }
- // Fix the darn stupid cookiename! (more may not be allowed, but these for sure!)
- if (isset($_POST['cookiename'])) {
- $_POST['cookiename'] = preg_replace('~[,;\s\.$]+~' . (Utils::$context['utf8'] ? 'u' : ''), '', $_POST['cookiename']);
- }
+ if (!str_starts_with($_POST['boardurl'], 'http://') && !str_starts_with($_POST['boardurl'], 'file://') && !str_starts_with($_POST['boardurl'], 'https://')) {
+ $_POST['boardurl'] = 'http://' . $_POST['boardurl'];
+ }
- // Fix the forum's URL if necessary.
- if (isset($_POST['boardurl'])) {
- if (str_ends_with($_POST['boardurl'], '/index.php')) {
- $_POST['boardurl'] = substr($_POST['boardurl'], 0, -10);
- } elseif (str_ends_with($_POST['boardurl'], '/')) {
- $_POST['boardurl'] = substr($_POST['boardurl'], 0, -1);
+ $_POST['boardurl'] = (string) new Url($_POST['boardurl'], true);
}
- if (!str_starts_with($_POST['boardurl'], 'http://') && !str_starts_with($_POST['boardurl'], 'file://') && !str_starts_with($_POST['boardurl'], 'https://')) {
- $_POST['boardurl'] = 'http://' . $_POST['boardurl'];
- }
+ // Any passwords?
+ $config_passwords = [];
- $_POST['boardurl'] = (string) new Url($_POST['boardurl'], true);
- }
+ // All the numeric variables.
+ $config_nums = [];
- // Any passwords?
- $config_passwords = [];
+ // All the checkboxes
+ $config_bools = [];
- // All the numeric variables.
- $config_nums = [];
+ // Ones that accept multiple types (should be rare)
+ $config_multis = [];
- // All the checkboxes
- $config_bools = [];
+ // Get all known setting definitions and assign them to our groups above.
+ $settings_defs = Config::getSettingsDefs();
- // Ones that accept multiple types (should be rare)
- $config_multis = [];
+ foreach ($settings_defs as $var => $def) {
+ if (!is_string($var)) {
+ continue;
+ }
- // Get all known setting definitions and assign them to our groups above.
- $settings_defs = Config::getSettingsDefs();
+ if (!empty($def['is_password'])) {
+ $config_passwords[] = $var;
+ } else {
+ // Special handling if multiple types are allowed.
+ if (is_array($def['type'])) {
+ // Obviously, we don't need null here.
+ $def['type'] = array_filter(
+ $def['type'],
+ function ($type) {
+ return $type !== 'NULL';
+ },
+ );
+
+ $type = count($def['type']) == 1 ? reset($def['type']) : 'multiple';
+ } else {
+ $type = $def['type'];
+ }
- foreach ($settings_defs as $var => $def) {
- if (!is_string($var)) {
- continue;
- }
+ switch ($type) {
+ case 'multiple':
+ $config_multis[$var] = $def['type'];
+ // no break
- if (!empty($def['is_password'])) {
- $config_passwords[] = $var;
- } else {
- // Special handling if multiple types are allowed.
- if (is_array($def['type'])) {
- // Obviously, we don't need null here.
- $def['type'] = array_filter(
- $def['type'],
- function ($type) {
- return $type !== 'NULL';
- },
- );
-
- $type = count($def['type']) == 1 ? reset($def['type']) : 'multiple';
- } else {
- $type = $def['type'];
- }
+ case 'double':
+ $config_nums[] = $var;
+ break;
- switch ($type) {
- case 'multiple':
- $config_multis[$var] = $def['type'];
- // no break
-
- case 'double':
- $config_nums[] = $var;
- break;
-
- case 'integer':
- // Some things saved as integers are presented as booleans
- foreach ($config_vars as $config_var) {
- if (is_array($config_var) && $config_var[0] == $var) {
- if ($config_var[3] == 'check') {
- $config_bools[] = $var;
- break 2;
+ case 'integer':
+ // Some things saved as integers are presented as booleans
+ foreach ($config_vars as $config_var) {
+ if (is_array($config_var) && $config_var[0] == $var) {
+ if ($config_var[3] == 'check') {
+ $config_bools[] = $var;
+ break 2;
+ }
+ break;
}
- break;
}
- }
- $config_nums[] = $var;
- break;
+ $config_nums[] = $var;
+ break;
- case 'boolean':
- $config_bools[] = $var;
- break;
+ case 'boolean':
+ $config_bools[] = $var;
+ break;
- default:
- break;
+ default:
+ break;
+ }
}
}
- }
- // Now sort everything into a big array, and figure out arrays and etc.
- $new_settings = [];
+ // Now sort everything into a big array, and figure out arrays and etc.
+ $new_settings = [];
- // Figure out which config vars we're saving here...
- foreach ($config_vars as $config_var) {
- if (!is_array($config_var) || $config_var[2] != 'file') {
- continue;
- }
+ // Figure out which config vars we're saving here...
+ foreach ($config_vars as $config_var) {
+ if (!is_array($config_var) || $config_var[2] != 'file') {
+ continue;
+ }
- $var_name = $config_var[0];
+ $var_name = $config_var[0];
- // Unknown setting?
- if (!isset($settings_defs[$var_name]) && isset($config_var[3])) {
- switch ($config_var[3]) {
- case 'int':
- case 'float':
- $config_nums[] = $var_name;
- break;
+ // Unknown setting?
+ if (!isset($settings_defs[$var_name]) && isset($config_var[3])) {
+ switch ($config_var[3]) {
+ case 'int':
+ case 'float':
+ $config_nums[] = $var_name;
+ break;
- case 'check':
- $config_bools[] = $var_name;
- break;
+ case 'check':
+ $config_bools[] = $var_name;
+ break;
- default:
- break;
+ default:
+ break;
+ }
}
- }
- if (!in_array($var_name, $config_bools) && !isset($_POST[$var_name])) {
- continue;
- }
-
- if (in_array($var_name, $config_passwords)) {
- if (isset($_POST[$var_name][1]) && $_POST[$var_name][0] == $_POST[$var_name][1]) {
- $new_settings[$var_name] = $_POST[$var_name][0];
+ if (!in_array($var_name, $config_bools) && !isset($_POST[$var_name])) {
+ continue;
}
- } elseif (in_array($var_name, $config_nums)) {
- $new_settings[$var_name] = (int) $_POST[$var_name];
- // If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
- $min = $config_var['min'] ?? 0;
- $new_settings[$var_name] = max($min, $new_settings[$var_name]);
+ if (in_array($var_name, $config_passwords)) {
+ if (isset($_POST[$var_name][1]) && $_POST[$var_name][0] == $_POST[$var_name][1]) {
+ $new_settings[$var_name] = $_POST[$var_name][0];
+ }
+ } elseif (in_array($var_name, $config_nums)) {
+ $new_settings[$var_name] = (int) $_POST[$var_name];
+
+ // If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
+ $min = $config_var['min'] ?? 0;
+ $new_settings[$var_name] = max($min, $new_settings[$var_name]);
- // Is there a max value for this as well?
- if (isset($config_var['max'])) {
- $new_settings[$var_name] = min($config_var['max'], $new_settings[$var_name]);
- }
- } elseif (in_array($var_name, $config_bools)) {
- $new_settings[$var_name] = !empty($_POST[$var_name]);
- } elseif (isset($config_multis[$var_name])) {
- $is_acceptable_type = false;
-
- foreach ($config_multis[$var_name] as $type) {
- $temp = $_POST[$var_name];
- settype($temp, $type);
-
- if ($temp == $_POST[$var_name]) {
- $new_settings[$var_name] = $temp;
- $is_acceptable_type = true;
- break;
+ // Is there a max value for this as well?
+ if (isset($config_var['max'])) {
+ $new_settings[$var_name] = min($config_var['max'], $new_settings[$var_name]);
+ }
+ } elseif (in_array($var_name, $config_bools)) {
+ $new_settings[$var_name] = !empty($_POST[$var_name]);
+ } elseif (isset($config_multis[$var_name])) {
+ $is_acceptable_type = false;
+
+ foreach ($config_multis[$var_name] as $type) {
+ $temp = $_POST[$var_name];
+ settype($temp, $type);
+
+ if ($temp == $_POST[$var_name]) {
+ $new_settings[$var_name] = $temp;
+ $is_acceptable_type = true;
+ break;
+ }
}
- }
- if (!$is_acceptable_type) {
- ErrorHandler::fatal('Invalid config_var \'' . $var_name . '\'');
+ if (!$is_acceptable_type) {
+ ErrorHandler::fatal('Invalid config_var \'' . $var_name . '\'');
+ }
+ } else {
+ $new_settings[$var_name] = $_POST[$var_name];
}
- } else {
- $new_settings[$var_name] = $_POST[$var_name];
}
- }
- // Save the relevant settings in the Settings.php file.
- Config::updateSettingsFile($new_settings);
+ // Save the relevant settings in the Settings.php file.
+ Config::updateSettingsFile($new_settings);
- // Now loop through the remaining (database-based) settings.
- $new_settings = [];
+ // Now loop through the remaining (database-based) settings.
+ $new_settings = [];
- foreach ($config_vars as $config_var) {
- // We just saved the file-based settings, so skip their definitions.
- if (!is_array($config_var) || $config_var[2] == 'file') {
- continue;
- }
+ foreach ($config_vars as $config_var) {
+ // We just saved the file-based settings, so skip their definitions.
+ if (!is_array($config_var) || $config_var[2] == 'file') {
+ continue;
+ }
- $new_setting = [$config_var[3], $config_var[0]];
+ $new_setting = [$config_var[3], $config_var[0]];
- // Select options need carried over, too.
- if (isset($config_var[4])) {
- $new_setting[] = $config_var[4];
- }
+ // Select options need carried over, too.
+ if (isset($config_var[4])) {
+ $new_setting[] = $config_var[4];
+ }
- // Include min and max if necessary
- if (isset($config_var['min'])) {
- $new_setting['min'] = $config_var['min'];
- }
+ // Include min and max if necessary
+ if (isset($config_var['min'])) {
+ $new_setting['min'] = $config_var['min'];
+ }
- if (isset($config_var['max'])) {
- $new_setting['max'] = $config_var['max'];
- }
+ if (isset($config_var['max'])) {
+ $new_setting['max'] = $config_var['max'];
+ }
- // Rewrite the definition a bit.
- $new_settings[] = $new_setting;
- }
+ // Rewrite the definition a bit.
+ $new_settings[] = $new_setting;
+ }
- // Save the new database-based settings, if any.
- if (!empty($new_settings)) {
- ACP::saveDBSettings($new_settings);
+ // Save the new database-based settings, if any.
+ if (!empty($new_settings)) {
+ ACP::saveDBSettings($new_settings);
+ }
}
- }
-
- /**
- * Helper function for saving database settings.
- *
- * @param array $config_vars An array of configuration variables
- */
- public static function saveDBSettings(array &$config_vars): void
- {
- static $board_list = null;
- SecurityToken::validate('admin-dbsc');
+ /**
+ * Helper function for saving database settings.
+ *
+ * @param array $config_vars An array of configuration variables
+ */
+ public static function saveDBSettings(array &$config_vars): void
+ {
+ static $board_list = null;
- $inlinePermissions = [];
+ SecurityToken::validate('admin-dbsc');
- foreach ($config_vars as $var) {
- if (!isset($var[1]) || (!isset($_POST[$var[1]]) && $var[0] != 'check' && $var[0] != 'permissions' && $var[0] != 'boards' && ($var[0] != 'bbc' || !isset($_POST[$var[1] . '_enabledTags'])))) {
- continue;
- }
+ $inlinePermissions = [];
- // Checkboxes!
- if ($var[0] == 'check') {
- $setArray[$var[1]] = !empty($_POST[$var[1]]) ? '1' : '0';
- }
- // Select boxes!
- elseif ($var[0] == 'select' && in_array($_POST[$var[1]], array_keys($var[2]))) {
- $setArray[$var[1]] = $_POST[$var[1]];
- } elseif ($var[0] == 'select' && !empty($var['multiple']) && array_intersect($_POST[$var[1]], array_keys($var[2])) != []) {
- // For security purposes we validate this line by line.
- $lOptions = [];
-
- foreach ($_POST[$var[1]] as $invar) {
- if (in_array($invar, array_keys($var[2]))) {
- $lOptions[] = $invar;
- }
+ foreach ($config_vars as $var) {
+ if (!isset($var[1]) || (!isset($_POST[$var[1]]) && $var[0] != 'check' && $var[0] != 'permissions' && $var[0] != 'boards' && ($var[0] != 'bbc' || !isset($_POST[$var[1] . '_enabledTags'])))) {
+ continue;
}
- $setArray[$var[1]] = Utils::jsonEncode($lOptions);
- }
- // List of boards!
- elseif ($var[0] == 'boards') {
- // We just need a simple list of valid boards, nothing more.
- if ($board_list === null) {
- $board_list = [];
- $request = Db::$db->query(
- '',
- 'SELECT id_board
- FROM {db_prefix}boards',
- );
-
- while ($row = Db::$db->fetch_row($request)) {
- $board_list[$row[0]] = true;
+ // Checkboxes!
+ if ($var[0] == 'check') {
+ $setArray[$var[1]] = !empty($_POST[$var[1]]) ? '1' : '0';
+ }
+ // Select boxes!
+ elseif ($var[0] == 'select' && in_array($_POST[$var[1]], array_keys($var[2]))) {
+ $setArray[$var[1]] = $_POST[$var[1]];
+ } elseif ($var[0] == 'select' && !empty($var['multiple']) && array_intersect($_POST[$var[1]], array_keys($var[2])) != []) {
+ // For security purposes we validate this line by line.
+ $lOptions = [];
+
+ foreach ($_POST[$var[1]] as $invar) {
+ if (in_array($invar, array_keys($var[2]))) {
+ $lOptions[] = $invar;
+ }
}
- Db::$db->free_result($request);
+
+ $setArray[$var[1]] = Utils::jsonEncode($lOptions);
}
+ // List of boards!
+ elseif ($var[0] == 'boards') {
+ // We just need a simple list of valid boards, nothing more.
+ if ($board_list === null) {
+ $board_list = [];
+ $request = Db::$db->query(
+ '',
+ 'SELECT id_board
+ FROM {db_prefix}boards',
+ );
+
+ while ($row = Db::$db->fetch_row($request)) {
+ $board_list[$row[0]] = true;
+ }
+ Db::$db->free_result($request);
+ }
- $lOptions = [];
+ $lOptions = [];
- if (!empty($_POST[$var[1]])) {
- foreach ($_POST[$var[1]] as $invar => $dummy) {
- if (isset($board_list[$invar])) {
- $lOptions[] = $invar;
+ if (!empty($_POST[$var[1]])) {
+ foreach ($_POST[$var[1]] as $invar => $dummy) {
+ if (isset($board_list[$invar])) {
+ $lOptions[] = $invar;
+ }
}
}
- }
- $setArray[$var[1]] = !empty($lOptions) ? implode(',', $lOptions) : '';
- }
- // Integers!
- elseif ($var[0] == 'int') {
- $setArray[$var[1]] = (int) $_POST[$var[1]];
+ $setArray[$var[1]] = !empty($lOptions) ? implode(',', $lOptions) : '';
+ }
+ // Integers!
+ elseif ($var[0] == 'int') {
+ $setArray[$var[1]] = (int) $_POST[$var[1]];
- // If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
- $min = $var['min'] ?? 0;
- $setArray[$var[1]] = max($min, $setArray[$var[1]]);
+ // If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
+ $min = $var['min'] ?? 0;
+ $setArray[$var[1]] = max($min, $setArray[$var[1]]);
- // Do we have a max value for this as well?
- if (isset($var['max'])) {
- $setArray[$var[1]] = min($var['max'], $setArray[$var[1]]);
+ // Do we have a max value for this as well?
+ if (isset($var['max'])) {
+ $setArray[$var[1]] = min($var['max'], $setArray[$var[1]]);
+ }
}
- }
- // Floating point!
- elseif ($var[0] == 'float') {
- $setArray[$var[1]] = (float) $_POST[$var[1]];
+ // Floating point!
+ elseif ($var[0] == 'float') {
+ $setArray[$var[1]] = (float) $_POST[$var[1]];
- // If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
- $min = $var['min'] ?? 0;
- $setArray[$var[1]] = max($min, $setArray[$var[1]]);
+ // If no min is specified, assume 0. This is done to avoid having to specify 'min => 0' for all settings where 0 is the min...
+ $min = $var['min'] ?? 0;
+ $setArray[$var[1]] = max($min, $setArray[$var[1]]);
- // Do we have a max value for this as well?
- if (isset($var['max'])) {
- $setArray[$var[1]] = min($var['max'], $setArray[$var[1]]);
+ // Do we have a max value for this as well?
+ if (isset($var['max'])) {
+ $setArray[$var[1]] = min($var['max'], $setArray[$var[1]]);
+ }
}
- }
- // Text!
- elseif (in_array($var[0], ['text', 'large_text', 'color', 'date', 'datetime', 'datetime-local', 'email', 'month', 'time'])) {
- $setArray[$var[1]] = $_POST[$var[1]];
- }
- // Passwords!
- elseif ($var[0] == 'password') {
- if (isset($_POST[$var[1]][1]) && $_POST[$var[1]][0] == $_POST[$var[1]][1]) {
- $setArray[$var[1]] = $_POST[$var[1]][0];
+ // Text!
+ elseif (in_array($var[0], ['text', 'large_text', 'color', 'date', 'datetime', 'datetime-local', 'email', 'month', 'time'])) {
+ $setArray[$var[1]] = $_POST[$var[1]];
}
- }
- // BBC.
- elseif ($var[0] == 'bbc') {
- $bbcTags = [];
-
- foreach (BBCodeParser::getCodes() as $tag) {
- $bbcTags[] = $tag['tag'];
+ // Passwords!
+ elseif ($var[0] == 'password') {
+ if (isset($_POST[$var[1]][1]) && $_POST[$var[1]][0] == $_POST[$var[1]][1]) {
+ $setArray[$var[1]] = $_POST[$var[1]][0];
+ }
}
+ // BBC.
+ elseif ($var[0] == 'bbc') {
+ $bbcTags = [];
- if (!isset($_POST[$var[1] . '_enabledTags'])) {
- $_POST[$var[1] . '_enabledTags'] = [];
- } elseif (!is_array($_POST[$var[1] . '_enabledTags'])) {
- $_POST[$var[1] . '_enabledTags'] = [$_POST[$var[1] . '_enabledTags']];
- }
+ foreach (BBCodeParser::getCodes() as $tag) {
+ $bbcTags[] = $tag['tag'];
+ }
+
+ if (!isset($_POST[$var[1] . '_enabledTags'])) {
+ $_POST[$var[1] . '_enabledTags'] = [];
+ } elseif (!is_array($_POST[$var[1] . '_enabledTags'])) {
+ $_POST[$var[1] . '_enabledTags'] = [$_POST[$var[1] . '_enabledTags']];
+ }
- $setArray[$var[1]] = implode(',', array_diff($bbcTags, $_POST[$var[1] . '_enabledTags']));
+ $setArray[$var[1]] = implode(',', array_diff($bbcTags, $_POST[$var[1] . '_enabledTags']));
+ }
+ // Permissions?
+ elseif ($var[0] == 'permissions') {
+ $inlinePermissions[] = $var[1];
+ }
}
- // Permissions?
- elseif ($var[0] == 'permissions') {
- $inlinePermissions[] = $var[1];
+
+ if (!empty($setArray)) {
+ Config::updateModSettings($setArray);
}
- }
- if (!empty($setArray)) {
- Config::updateModSettings($setArray);
+ // If we have inline permissions we need to save them.
+ if (!empty($inlinePermissions) && User::$me->allowedTo('manage_permissions')) {
+ Permissions::save_inline_permissions($inlinePermissions);
+ }
}
- // If we have inline permissions we need to save them.
- if (!empty($inlinePermissions) && User::$me->allowedTo('manage_permissions')) {
- Permissions::save_inline_permissions($inlinePermissions);
- }
- }
+ /**
+ * Get a list of versions that are currently installed on the server.
+ *
+ * @param array $checkFor An array of what to check versions for - can contain one or more of 'gd', 'imagemagick', 'db_server', 'phpa', 'memcache', 'php' or 'server'
+ * @return array An array of versions (keys are same as what was in $checkFor, values are the versions)
+ */
+ public static function getServerVersions(array $checkFor): array
+ {
+ Lang::load('Admin');
+ Lang::load('ManageSettings');
+
+ $versions = [];
+
+ // Is GD available? If it is, we should show version information for it too.
+ if (in_array('gd', $checkFor) && function_exists('gd_info')) {
+ $temp = gd_info();
+ $versions['gd'] = ['title' => Lang::$txt['support_versions_gd'], 'version' => $temp['GD Version']];
+ }
- /**
- * Get a list of versions that are currently installed on the server.
- *
- * @param array $checkFor An array of what to check versions for - can contain one or more of 'gd', 'imagemagick', 'db_server', 'phpa', 'memcache', 'php' or 'server'
- * @return array An array of versions (keys are same as what was in $checkFor, values are the versions)
- */
- public static function getServerVersions(array $checkFor): array
- {
- Lang::load('Admin');
- Lang::load('ManageSettings');
+ // Why not have a look at ImageMagick? If it's installed, we should show version information for it too.
+ if (in_array('imagemagick', $checkFor) && class_exists('Imagick')) {
+ $temp = new \Imagick();
+ $temp2 = $temp->getVersion();
+ $im_version = $temp2['versionString'];
+ $extension_version = 'Imagick ' . phpversion('Imagick');
- $versions = [];
+ // We already know it's ImageMagick and the website isn't needed...
+ $im_version = str_replace(['ImageMagick ', ' https://www.imagemagick.org'], '', $im_version);
- // Is GD available? If it is, we should show version information for it too.
- if (in_array('gd', $checkFor) && function_exists('gd_info')) {
- $temp = gd_info();
- $versions['gd'] = ['title' => Lang::$txt['support_versions_gd'], 'version' => $temp['GD Version']];
- }
+ $versions['imagemagick'] = ['title' => Lang::$txt['support_versions_imagemagick'], 'version' => $im_version . ' (' . $extension_version . ')'];
+ }
- // Why not have a look at ImageMagick? If it's installed, we should show version information for it too.
- if (in_array('imagemagick', $checkFor) && class_exists('Imagick')) {
- $temp = new \Imagick();
- $temp2 = $temp->getVersion();
- $im_version = $temp2['versionString'];
- $extension_version = 'Imagick ' . phpversion('Imagick');
+ // Now lets check for the Database.
+ if (in_array('db_server', $checkFor)) {
+ if (!isset(Db::$db_connection) || Db::$db_connection === false) {
+ Lang::load('Errors');
+ trigger_error(Lang::$txt['get_server_versions_no_database'], E_USER_NOTICE);
+ } else {
+ $versions['db_engine'] = [
+ 'title' => Lang::getTxt('support_versions_db_engine', ['db_title' => Db::$db->title]),
+ 'version' => Db::$db->get_vendor(),
+ ];
- // We already know it's ImageMagick and the website isn't needed...
- $im_version = str_replace(['ImageMagick ', ' https://www.imagemagick.org'], '', $im_version);
+ $versions['db_server'] = [
+ 'title' => Lang::getTxt('support_versions_db', ['db_title' => Db::$db->title]),
+ 'version' => Db::$db->get_version(),
+ ];
+ }
+ }
- $versions['imagemagick'] = ['title' => Lang::$txt['support_versions_imagemagick'], 'version' => $im_version . ' (' . $extension_version . ')'];
- }
+ // Check to see if we have any accelerators installed.
+ foreach (CacheApi::detect() as $class_name => $cache_api) {
+ $class_name_txt_key = strtolower($cache_api->getImplementationClassKeyName());
- // Now lets check for the Database.
- if (in_array('db_server', $checkFor)) {
- if (!isset(Db::$db_connection) || Db::$db_connection === false) {
- Lang::load('Errors');
- trigger_error(Lang::$txt['get_server_versions_no_database'], E_USER_NOTICE);
- } else {
- $versions['db_engine'] = [
- 'title' => Lang::getTxt('support_versions_db_engine', ['db_title' => Db::$db->title]),
- 'version' => Db::$db->get_vendor(),
- ];
+ if (in_array($class_name_txt_key, $checkFor)) {
+ $versions[$class_name_txt_key] = [
+ 'title' => Lang::$txt[$class_name_txt_key . '_cache'] ?? $class_name,
+ 'version' => $cache_api->getVersion(),
+ ];
+ }
+ }
- $versions['db_server'] = [
- 'title' => Lang::getTxt('support_versions_db', ['db_title' => Db::$db->title]),
- 'version' => Db::$db->get_version(),
+ if (in_array('php', $checkFor)) {
+ $versions['php'] = [
+ 'title' => 'PHP',
+ 'version' => PHP_VERSION,
+ 'more' => '?action=admin;area=serversettings;sa=phpinfo',
];
}
- }
- // Check to see if we have any accelerators installed.
- foreach (CacheApi::detect() as $class_name => $cache_api) {
- $class_name_txt_key = strtolower($cache_api->getImplementationClassKeyName());
-
- if (in_array($class_name_txt_key, $checkFor)) {
- $versions[$class_name_txt_key] = [
- 'title' => Lang::$txt[$class_name_txt_key . '_cache'] ?? $class_name,
- 'version' => $cache_api->getVersion(),
+ if (in_array('server', $checkFor)) {
+ $versions['server'] = [
+ 'title' => Lang::$txt['support_versions_server'],
+ 'version' => $_SERVER['SERVER_SOFTWARE'],
];
}
+
+ return $versions;
}
- if (in_array('php', $checkFor)) {
- $versions['php'] = [
- 'title' => 'PHP',
- 'version' => PHP_VERSION,
- 'more' => '?action=admin;area=serversettings;sa=phpinfo',
+ /**
+ * Search through source, theme, and language files to determine their version.
+ * Get detailed version information about the physical SMF files on the server.
+ *
+ * - the input parameter allows to set whether to include SSI.php and whether
+ * the results should be sorted.
+ * - returns an array containing information on source files, templates, and
+ * language files found in the default theme directory (grouped by language).
+ *
+ * @param array &$versionOptions An array of options. Can contain one or more of 'include_root', 'include_tasks' and 'sort_results'
+ * @return array An array of file version info.
+ */
+ public static function getFileVersions(array &$versionOptions): array
+ {
+ // Default place to find the languages would be the default theme dir.
+ $lang_dir = Theme::$current->settings['default_theme_dir'] . '/languages';
+
+ $version_info = [
+ 'root_versions' => [],
+ 'file_versions' => [],
+ 'default_template_versions' => [],
+ 'template_versions' => [],
+ 'default_language_versions' => [],
+ 'tasks_versions' => [],
];
- }
- if (in_array('server', $checkFor)) {
- $versions['server'] = [
- 'title' => Lang::$txt['support_versions_server'],
- 'version' => $_SERVER['SERVER_SOFTWARE'],
+ $root_files = [
+ 'cron.php',
+ 'proxy.php',
+ 'SSI.php',
+ 'subscriptions.php',
];
- }
- return $versions;
- }
+ // Find the version in root files header.
+ if (!empty($versionOptions['include_root'])) {
+ foreach ($root_files as $file) {
+ if (!file_exists(Config::$boarddir . '/' . $file)) {
+ continue;
+ }
- /**
- * Search through source, theme, and language files to determine their version.
- * Get detailed version information about the physical SMF files on the server.
- *
- * - the input parameter allows to set whether to include SSI.php and whether
- * the results should be sorted.
- * - returns an array containing information on source files, templates, and
- * language files found in the default theme directory (grouped by language).
- *
- * @param array &$versionOptions An array of options. Can contain one or more of 'include_root', 'include_tasks' and 'sort_results'
- * @return array An array of file version info.
- */
- public static function getFileVersions(array &$versionOptions): array
- {
- // Default place to find the languages would be the default theme dir.
- $lang_dir = Theme::$current->settings['default_theme_dir'] . '/languages';
-
- $version_info = [
- 'root_versions' => [],
- 'file_versions' => [],
- 'default_template_versions' => [],
- 'template_versions' => [],
- 'default_language_versions' => [],
- 'tasks_versions' => [],
- ];
+ $fp = fopen(Config::$boarddir . '/' . $file, 'rb');
+ $header = fread($fp, 4096);
+ fclose($fp);
- $root_files = [
- 'cron.php',
- 'proxy.php',
- 'SSI.php',
- 'subscriptions.php',
- ];
+ // The comment looks roughly like... that.
+ if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) {
+ $version_info['root_versions'][$file] = $match[1];
+ }
+ // Not found! This is bad.
+ else {
+ $version_info['root_versions'][$file] = '??';
+ }
+ }
+ }
+
+ // Load all the files in the Sources directory, except some vendor libraries, index place holders and non php files.
+ $sources_dir = new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator(
+ Config::$sourcedir,
+ \RecursiveDirectoryIterator::SKIP_DOTS,
+ ),
+ );
+
+ $ignore_sources = [
+ Config::$sourcedir . '/minify/*',
+ Config::$sourcedir . '/ReCaptcha/*',
+ Config::$sourcedir . '/Tasks/*',
+ ];
- // Find the version in root files header.
- if (!empty($versionOptions['include_root'])) {
- foreach ($root_files as $file) {
- if (!file_exists(Config::$boarddir . '/' . $file)) {
+ foreach ($sources_dir as $filename => $file) {
+ if (!$file->isFile() || $file->getFilename() === 'index.php' || $file->getExtension() !== 'php') {
continue;
}
- $fp = fopen(Config::$boarddir . '/' . $file, 'rb');
- $header = fread($fp, 4096);
- fclose($fp);
+ foreach ($ignore_sources as $if) {
+ if (preg_match('~' . $if . '~i', $filename)) {
+ continue 2;
+ }
+ }
+
+ $shortname = str_replace(Config::$sourcedir . '/', '', $filename);
- // The comment looks roughly like... that.
+ // Read the first 4k from the file.... enough for the header.
+ $fp = $file->openFile('rb');
+ $header = $fp->fread(4096);
+ $fp = null;
+
+ // Look for the version comment in the file header.
if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) {
- $version_info['root_versions'][$file] = $match[1];
+ $version_info['file_versions'][$shortname] = $match[1];
}
- // Not found! This is bad.
+ // It wasn't found, but the file was... show a '??'.
else {
- $version_info['root_versions'][$file] = '??';
+ $version_info['file_versions'][$shortname] = '??';
}
}
- }
-
- // Load all the files in the Sources directory, except some vendor libraries, index place holders and non php files.
- $sources_dir = new \RecursiveIteratorIterator(
- new \RecursiveDirectoryIterator(
- Config::$sourcedir,
- \RecursiveDirectoryIterator::SKIP_DOTS,
- ),
- );
-
- $ignore_sources = [
- Config::$sourcedir . '/minify/*',
- Config::$sourcedir . '/ReCaptcha/*',
- Config::$sourcedir . '/Tasks/*',
- ];
-
- foreach ($sources_dir as $filename => $file) {
- if (!$file->isFile() || $file->getFilename() === 'index.php' || $file->getExtension() !== 'php') {
- continue;
- }
-
- foreach ($ignore_sources as $if) {
- if (preg_match('~' . $if . '~i', $filename)) {
- continue 2;
+ $sources_dir = null;
+
+ // Load all the files in the tasks directory.
+ if (!empty($versionOptions['include_tasks'])) {
+ $tasks_dir = dir(Config::$tasksdir);
+
+ while ($entry = $tasks_dir->read()) {
+ if (str_ends_with($entry, '.php') && !is_dir(Config::$tasksdir . '/' . $entry) && $entry !== 'index.php') {
+ // Read the first 4k from the file.... enough for the header.
+ $fp = fopen(Config::$tasksdir . '/' . $entry, 'rb');
+ $header = fread($fp, 4096);
+ fclose($fp);
+
+ // Look for the version comment in the file header.
+ if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) {
+ $version_info['tasks_versions'][$entry] = $match[1];
+ }
+ // It wasn't found, but the file was... show a '??'.
+ else {
+ $version_info['tasks_versions'][$entry] = '??';
+ }
+ }
}
+ $tasks_dir->close();
}
- $shortname = str_replace(Config::$sourcedir . '/', '', $filename);
+ // Load all the files in the default template directory - and the current theme if applicable.
+ $directories = ['default_template_versions' => Theme::$current->settings['default_theme_dir']];
- // Read the first 4k from the file.... enough for the header.
- $fp = $file->openFile('rb');
- $header = $fp->fread(4096);
- $fp = null;
-
- // Look for the version comment in the file header.
- if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) {
- $version_info['file_versions'][$shortname] = $match[1];
- }
- // It wasn't found, but the file was... show a '??'.
- else {
- $version_info['file_versions'][$shortname] = '??';
+ if (Theme::$current->settings['theme_id'] != 1) {
+ $directories += ['template_versions' => Theme::$current->settings['theme_dir']];
}
- }
- $sources_dir = null;
- // Load all the files in the tasks directory.
- if (!empty($versionOptions['include_tasks'])) {
- $tasks_dir = dir(Config::$tasksdir);
+ foreach ($directories as $type => $dirname) {
+ $this_dir = dir($dirname);
- while ($entry = $tasks_dir->read()) {
- if (str_ends_with($entry, '.php') && !is_dir(Config::$tasksdir . '/' . $entry) && $entry !== 'index.php') {
- // Read the first 4k from the file.... enough for the header.
- $fp = fopen(Config::$tasksdir . '/' . $entry, 'rb');
- $header = fread($fp, 4096);
- fclose($fp);
+ while ($entry = $this_dir->read()) {
+ if (str_ends_with($entry, 'template.php') && !is_dir($dirname . '/' . $entry)) {
+ // Read the first 768 bytes from the file.... enough for the header.
+ $fp = fopen($dirname . '/' . $entry, 'rb');
+ $header = fread($fp, 768);
+ fclose($fp);
- // Look for the version comment in the file header.
- if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) {
- $version_info['tasks_versions'][$entry] = $match[1];
- }
- // It wasn't found, but the file was... show a '??'.
- else {
- $version_info['tasks_versions'][$entry] = '??';
+ // Look for the version comment in the file header.
+ if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) {
+ $version_info[$type][$entry] = $match[1];
+ }
+ // It wasn't found, but the file was... show a '??'.
+ else {
+ $version_info[$type][$entry] = '??';
+ }
}
}
+ $this_dir->close();
}
- $tasks_dir->close();
- }
-
- // Load all the files in the default template directory - and the current theme if applicable.
- $directories = ['default_template_versions' => Theme::$current->settings['default_theme_dir']];
-
- if (Theme::$current->settings['theme_id'] != 1) {
- $directories += ['template_versions' => Theme::$current->settings['theme_dir']];
- }
- foreach ($directories as $type => $dirname) {
- $this_dir = dir($dirname);
+ // Load up all the files in the default language directory and sort by language.
+ $this_dir = dir($lang_dir);
while ($entry = $this_dir->read()) {
- if (str_ends_with($entry, 'template.php') && !is_dir($dirname . '/' . $entry)) {
+ if (str_ends_with($entry, '.php') && $entry != 'index.php' && !is_dir($lang_dir . '/' . $entry)) {
// Read the first 768 bytes from the file.... enough for the header.
- $fp = fopen($dirname . '/' . $entry, 'rb');
+ $fp = fopen($lang_dir . '/' . $entry, 'rb');
$header = fread($fp, 768);
fclose($fp);
+ // Split the file name off into useful bits.
+ list($name, $language) = explode('.', $entry);
+
// Look for the version comment in the file header.
- if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1) {
- $version_info[$type][$entry] = $match[1];
+ if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1) {
+ $version_info['default_language_versions'][$language][$name] = $match[1];
}
// It wasn't found, but the file was... show a '??'.
else {
- $version_info[$type][$entry] = '??';
+ $version_info['default_language_versions'][$language][$name] = '??';
}
}
}
- $this_dir->close();
- }
- // Load up all the files in the default language directory and sort by language.
- $this_dir = dir($lang_dir);
-
- while ($entry = $this_dir->read()) {
- if (str_ends_with($entry, '.php') && $entry != 'index.php' && !is_dir($lang_dir . '/' . $entry)) {
- // Read the first 768 bytes from the file.... enough for the header.
- $fp = fopen($lang_dir . '/' . $entry, 'rb');
- $header = fread($fp, 768);
- fclose($fp);
-
- // Split the file name off into useful bits.
- list($name, $language) = explode('.', $entry);
+ $this_dir->close();
- // Look for the version comment in the file header.
- if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1) {
- $version_info['default_language_versions'][$language][$name] = $match[1];
- }
- // It wasn't found, but the file was... show a '??'.
- else {
- $version_info['default_language_versions'][$language][$name] = '??';
+ // Sort the file versions by filename.
+ if (!empty($versionOptions['sort_results'])) {
+ ksort($version_info['file_versions']);
+ ksort($version_info['default_template_versions']);
+ ksort($version_info['template_versions']);
+ ksort($version_info['default_language_versions']);
+ ksort($version_info['tasks_versions']);
+
+ // For languages sort each language too.
+ foreach ($version_info['default_language_versions'] as $language => $dummy) {
+ ksort($version_info['default_language_versions'][$language]);
}
}
- }
- $this_dir->close();
-
- // Sort the file versions by filename.
- if (!empty($versionOptions['sort_results'])) {
- ksort($version_info['file_versions']);
- ksort($version_info['default_template_versions']);
- ksort($version_info['template_versions']);
- ksort($version_info['default_language_versions']);
- ksort($version_info['tasks_versions']);
-
- // For languages sort each language too.
- foreach ($version_info['default_language_versions'] as $language => $dummy) {
- ksort($version_info['default_language_versions'][$language]);
- }
+ return $version_info;
}
- return $version_info;
- }
+ /**
+ * Saves the admin's current preferences to the database.
+ */
+ public static function updateAdminPreferences(): void
+ {
+ // This must exist!
+ if (!isset(Utils::$context['admin_preferences'])) {
+ return;
+ }
- /**
- * Saves the admin's current preferences to the database.
- */
- public static function updateAdminPreferences(): void
- {
- // This must exist!
- if (!isset(Utils::$context['admin_preferences'])) {
- return;
+ // This is what we'll be saving.
+ Theme::$current->options['admin_preferences'] = Utils::jsonEncode(Utils::$context['admin_preferences']);
+
+ // Just check we haven't ended up with something theme exclusive somehow.
+ Db::$db->query(
+ '',
+ 'DELETE FROM {db_prefix}themes
+ WHERE id_theme != {int:default_theme}
+ AND variable = {string:admin_preferences}',
+ [
+ 'default_theme' => 1,
+ 'admin_preferences' => 'admin_preferences',
+ ],
+ );
+
+ // Update the themes table.
+ Db::$db->insert(
+ 'replace',
+ '{db_prefix}themes',
+ ['id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'],
+ [User::$me->id, 1, 'admin_preferences', Theme::$current->options['admin_preferences']],
+ ['id_member', 'id_theme', 'variable'],
+ );
+
+ // Make sure we invalidate any cache.
+ CacheApi::put('theme_settings-' . Theme::$current->settings['theme_id'] . ':' . User::$me->id, null, 0);
}
- // This is what we'll be saving.
- Theme::$current->options['admin_preferences'] = Utils::jsonEncode(Utils::$context['admin_preferences']);
-
- // Just check we haven't ended up with something theme exclusive somehow.
- Db::$db->query(
- '',
- 'DELETE FROM {db_prefix}themes
- WHERE id_theme != {int:default_theme}
- AND variable = {string:admin_preferences}',
- [
- 'default_theme' => 1,
- 'admin_preferences' => 'admin_preferences',
- ],
- );
-
- // Update the themes table.
- Db::$db->insert(
- 'replace',
- '{db_prefix}themes',
- ['id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'],
- [User::$me->id, 1, 'admin_preferences', Theme::$current->options['admin_preferences']],
- ['id_member', 'id_theme', 'variable'],
- );
-
- // Make sure we invalidate any cache.
- CacheApi::put('theme_settings-' . Theme::$current->settings['theme_id'] . ':' . User::$me->id, null, 0);
- }
+ /**
+ * Send all the administrators a lovely email.
+ * - loads all users who are admins or have the admin forum permission.
+ * - uses the email template and replacements passed in the parameters.
+ * - sends them an email.
+ *
+ * @param string $template Which email template to use
+ * @param array $replacements An array of items to replace the variables in the template
+ * @param array $additional_recipients An array of arrays of info for additional recipients. Should have 'id', 'email' and 'name' for each.
+ */
+ public static function emailAdmins(string $template, array $replacements = [], array $additional_recipients = []): void
+ {
+ // Load all members which are effectively admins.
+ $members = User::membersAllowedTo('admin_forum');
+
+ // Load their alert preferences
+ $prefs = Notify::getNotifyPrefs($members, 'announcements', true);
+
+ $emails_sent = [];
+
+ $request = Db::$db->query(
+ '',
+ 'SELECT id_member, member_name, real_name, lngfile, email_address
+ FROM {db_prefix}members
+ WHERE id_member IN({array_int:members})',
+ [
+ 'members' => $members,
+ ],
+ );
- /**
- * Send all the administrators a lovely email.
- * - loads all users who are admins or have the admin forum permission.
- * - uses the email template and replacements passed in the parameters.
- * - sends them an email.
- *
- * @param string $template Which email template to use
- * @param array $replacements An array of items to replace the variables in the template
- * @param array $additional_recipients An array of arrays of info for additional recipients. Should have 'id', 'email' and 'name' for each.
- */
- public static function emailAdmins(string $template, array $replacements = [], array $additional_recipients = []): void
- {
- // Load all members which are effectively admins.
- $members = User::membersAllowedTo('admin_forum');
+ while ($row = Db::$db->fetch_assoc($request)) {
+ if (empty($prefs[$row['id_member']]['announcements'])) {
+ continue;
+ }
- // Load their alert preferences
- $prefs = Notify::getNotifyPrefs($members, 'announcements', true);
+ // Stick their particulars in the replacement data.
+ $replacements['IDMEMBER'] = $row['id_member'];
+ $replacements['REALNAME'] = $row['member_name'];
+ $replacements['USERNAME'] = $row['real_name'];
- $emails_sent = [];
+ // Load the data from the template.
+ $emaildata = Mail::loadEmailTemplate($template, $replacements, empty($row['lngfile']) || empty(Config::$modSettings['userLanguage']) ? Lang::$default : $row['lngfile']);
- $request = Db::$db->query(
- '',
- 'SELECT id_member, member_name, real_name, lngfile, email_address
- FROM {db_prefix}members
- WHERE id_member IN({array_int:members})',
- [
- 'members' => $members,
- ],
- );
+ // Then send the actual email.
+ Mail::send($row['email_address'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
- while ($row = Db::$db->fetch_assoc($request)) {
- if (empty($prefs[$row['id_member']]['announcements'])) {
- continue;
+ // Track who we emailed so we don't do it twice.
+ $emails_sent[] = $row['email_address'];
}
+ Db::$db->free_result($request);
- // Stick their particulars in the replacement data.
- $replacements['IDMEMBER'] = $row['id_member'];
- $replacements['REALNAME'] = $row['member_name'];
- $replacements['USERNAME'] = $row['real_name'];
+ // Any additional users we must email this to?
+ if (!empty($additional_recipients)) {
+ foreach ($additional_recipients as $recipient) {
+ if (in_array($recipient['email'], $emails_sent)) {
+ continue;
+ }
- // Load the data from the template.
- $emaildata = Mail::loadEmailTemplate($template, $replacements, empty($row['lngfile']) || empty(Config::$modSettings['userLanguage']) ? Lang::$default : $row['lngfile']);
+ $replacements['IDMEMBER'] = $recipient['id'];
+ $replacements['REALNAME'] = $recipient['name'];
+ $replacements['USERNAME'] = $recipient['name'];
- // Then send the actual email.
- Mail::send($row['email_address'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
+ // Load the template again.
+ $emaildata = Mail::loadEmailTemplate($template, $replacements, empty($recipient['lang']) || empty(Config::$modSettings['userLanguage']) ? Lang::$default : $recipient['lang']);
- // Track who we emailed so we don't do it twice.
- $emails_sent[] = $row['email_address'];
+ // Send off the email.
+ Mail::send($recipient['email'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
+ }
+ }
}
- Db::$db->free_result($request);
- // Any additional users we must email this to?
- if (!empty($additional_recipients)) {
- foreach ($additional_recipients as $recipient) {
- if (in_array($recipient['email'], $emails_sent)) {
- continue;
+ /**
+ * Question the verity of the admin by asking for his or her password.
+ * - loads Login.template.php and uses the admin_login sub template.
+ * - sends data to template so the admin is sent on to the page they
+ * wanted if their password is correct, otherwise they can try again.
+ *
+ * @param string $type What login type is this - can be 'admin' or 'moderate'
+ */
+ public static function adminLogin(string $type = 'admin'): void
+ {
+ Lang::load('Admin');
+ Theme::loadTemplate('Login');
+
+ // Validate what type of session check this is.
+ $types = [];
+ IntegrationHook::call('integrate_validateSession', [&$types]);
+ $type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin';
+
+ // They used a wrong password, log it and unset that.
+ if (isset($_POST[$type . '_hash_pass']) || isset($_POST[$type . '_pass'])) {
+ Lang::$txt['security_wrong'] = Lang::getTxt('security_wrong', ['referrer' => $_SERVER['HTTP_REFERER'] ?? Lang::$txt['unknown'], 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'ip' => User::$me->ip]);
+ ErrorHandler::log(Lang::$txt['security_wrong'], 'critical');
+
+ if (isset($_POST[$type . '_hash_pass'])) {
+ unset($_POST[$type . '_hash_pass']);
}
- $replacements['IDMEMBER'] = $recipient['id'];
- $replacements['REALNAME'] = $recipient['name'];
- $replacements['USERNAME'] = $recipient['name'];
-
- // Load the template again.
- $emaildata = Mail::loadEmailTemplate($template, $replacements, empty($recipient['lang']) || empty(Config::$modSettings['userLanguage']) ? Lang::$default : $recipient['lang']);
+ if (isset($_POST[$type . '_pass'])) {
+ unset($_POST[$type . '_pass']);
+ }
- // Send off the email.
- Mail::send($recipient['email'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
+ Utils::$context['incorrect_password'] = true;
}
- }
- }
-
- /**
- * Question the verity of the admin by asking for his or her password.
- * - loads Login.template.php and uses the admin_login sub template.
- * - sends data to template so the admin is sent on to the page they
- * wanted if their password is correct, otherwise they can try again.
- *
- * @param string $type What login type is this - can be 'admin' or 'moderate'
- */
- public static function adminLogin(string $type = 'admin'): void
- {
- Lang::load('Admin');
- Theme::loadTemplate('Login');
- // Validate what type of session check this is.
- $types = [];
- IntegrationHook::call('integrate_validateSession', [&$types]);
- $type = in_array($type, $types) || $type == 'moderate' ? $type : 'admin';
+ SecurityToken::create('admin-login');
- // They used a wrong password, log it and unset that.
- if (isset($_POST[$type . '_hash_pass']) || isset($_POST[$type . '_pass'])) {
- Lang::$txt['security_wrong'] = Lang::getTxt('security_wrong', ['referrer' => $_SERVER['HTTP_REFERER'] ?? Lang::$txt['unknown'], 'user_agent' => $_SERVER['HTTP_USER_AGENT'], 'ip' => User::$me->ip]);
- ErrorHandler::log(Lang::$txt['security_wrong'], 'critical');
+ // Figure out the get data and post data.
+ Utils::$context['get_data'] = '?' . self::construct_query_string($_GET);
+ Utils::$context['post_data'] = '';
- if (isset($_POST[$type . '_hash_pass'])) {
- unset($_POST[$type . '_hash_pass']);
- }
+ // Now go through $_POST. Make sure the session hash is sent.
+ $_POST[Utils::$context['session_var']] = Utils::$context['session_id'];
- if (isset($_POST[$type . '_pass'])) {
- unset($_POST[$type . '_pass']);
+ foreach ($_POST as $k => $v) {
+ Utils::$context['post_data'] .= self::adminLogin_outputPostVars($k, $v);
}
- Utils::$context['incorrect_password'] = true;
- }
-
- SecurityToken::create('admin-login');
-
- // Figure out the get data and post data.
- Utils::$context['get_data'] = '?' . self::construct_query_string($_GET);
- Utils::$context['post_data'] = '';
+ // Now we'll use the admin_login sub template of the Login template.
+ Utils::$context['sub_template'] = 'admin_login';
- // Now go through $_POST. Make sure the session hash is sent.
- $_POST[Utils::$context['session_var']] = Utils::$context['session_id'];
+ // And title the page something like "Login".
+ if (!isset(Utils::$context['page_title'])) {
+ Utils::$context['page_title'] = Lang::$txt['login'];
+ }
- foreach ($_POST as $k => $v) {
- Utils::$context['post_data'] .= self::adminLogin_outputPostVars($k, $v);
- }
+ // The type of action.
+ Utils::$context['sessionCheckType'] = $type;
- // Now we'll use the admin_login sub template of the Login template.
- Utils::$context['sub_template'] = 'admin_login';
+ Utils::obExit();
- // And title the page something like "Login".
- if (!isset(Utils::$context['page_title'])) {
- Utils::$context['page_title'] = Lang::$txt['login'];
+ // We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged.
+ trigger_error('No direct access...', E_USER_ERROR);
}
- // The type of action.
- Utils::$context['sessionCheckType'] = $type;
-
- Utils::obExit();
-
- // We MUST exit at this point, because otherwise we CANNOT KNOW that the user is privileged.
- trigger_error('No direct access...', E_USER_ERROR);
- }
-
- /******************
- * Internal methods
- ******************/
+ /******************
+ * Internal methods
+ ******************/
+
+ /**
+ * Constructor. Protected to force instantiation via self::load().
+ */
+ protected function __construct()
+ {
+ // Load the language and templates....
+ Lang::load('Admin');
+ Theme::loadTemplate('Admin');
+ Theme::loadJavaScriptFile('admin.js', ['minimize' => true], 'smf_admin');
+ Theme::loadCSSFile('admin.css', [], 'smf_admin');
+
+ // Set any dynamic values in $this->admin_areas.
+ $this->setAdminAreas();
+
+ // No indexing evil stuff.
+ Utils::$context['robot_no_index'] = true;
+
+ // Some preferences.
+ Utils::$context['admin_preferences'] = !empty(Theme::$current->options['admin_preferences']) ? Utils::jsonDecode(Theme::$current->options['admin_preferences'], true) : [];
+
+ // Any files to include for administration?
+ if (!empty(Config::$modSettings['integrate_admin_include'])) {
+ $admin_includes = explode(',', Config::$modSettings['integrate_admin_include']);
+
+ foreach ($admin_includes as $include) {
+ $include = strtr(trim($include), [
+ '$boarddir' => Config::$boarddir,
+ '$sourcedir' => Config::$sourcedir,
+ '$themedir' => Theme::$current->settings['theme_dir'],
+ ]);
- /**
- * Constructor. Protected to force instantiation via self::load().
- */
- protected function __construct()
- {
- // Load the language and templates....
- Lang::load('Admin');
- Theme::loadTemplate('Admin');
- Theme::loadJavaScriptFile('admin.js', ['minimize' => true], 'smf_admin');
- Theme::loadCSSFile('admin.css', [], 'smf_admin');
-
- // Set any dynamic values in $this->admin_areas.
- $this->setAdminAreas();
-
- // No indexing evil stuff.
- Utils::$context['robot_no_index'] = true;
-
- // Some preferences.
- Utils::$context['admin_preferences'] = !empty(Theme::$current->options['admin_preferences']) ? Utils::jsonDecode(Theme::$current->options['admin_preferences'], true) : [];
-
- // Any files to include for administration?
- if (!empty(Config::$modSettings['integrate_admin_include'])) {
- $admin_includes = explode(',', Config::$modSettings['integrate_admin_include']);
-
- foreach ($admin_includes as $include) {
- $include = strtr(trim($include), [
- '$boarddir' => Config::$boarddir,
- '$sourcedir' => Config::$sourcedir,
- '$themedir' => Theme::$current->settings['theme_dir'],
- ]);
-
- if (file_exists($include)) {
- require_once $include;
+ if (file_exists($include)) {
+ require_once $include;
+ }
}
}
}
- }
-
- /**
- * Sets any dynamic values in $this->admin_areas.
- */
- protected function setAdminAreas(): void
- {
- // Finalize various string values.
- array_walk_recursive(
- $this->admin_areas,
- function (&$value, $key) {
- if (in_array($key, ['title', 'label'])) {
- $value = Lang::$txt[$value] ?? $value;
- }
-
- if (is_string($value)) {
- $value = strtr($value, [
- '{scripturl}' => Config::$scripturl,
- '{boardurl}' => Config::$boardurl,
- ]);
- }
- },
- );
- // Fill in the ID number for the current theme URL.
- $this->admin_areas['config']['areas']['current_theme']['custom_url'] = sprintf($this->admin_areas['config']['areas']['current_theme']['custom_url'], Theme::$current->settings['theme_id']);
-
- // Figure out what is enabled or not.
- $this->admin_areas['forum']['areas']['adminlogoff']['enabled'] = empty(Config::$modSettings['securityDisable']);
+ /**
+ * Sets any dynamic values in $this->admin_areas.
+ */
+ protected function setAdminAreas(): void
+ {
+ // Finalize various string values.
+ array_walk_recursive(
+ $this->admin_areas,
+ function (&$value, $key) {
+ if (in_array($key, ['title', 'label'])) {
+ $value = Lang::$txt[$value] ?? $value;
+ }
- if (empty(Config::$modSettings['cal_enabled'])) {
- $this->admin_areas['layout']['areas']['managecalendar']['inactive'] = true;
- $this->admin_areas['layout']['areas']['managecalendar']['subsections'] = [];
- }
+ if (is_string($value)) {
+ $value = strtr($value, [
+ '{scripturl}' => Config::$scripturl,
+ '{boardurl}' => Config::$boardurl,
+ ]);
+ }
+ },
+ );
- $this->admin_areas['layout']['areas']['smileys']['subsections']['addsmiley']['enabled'] = !empty(Config::$modSettings['smiley_enable']);
- $this->admin_areas['layout']['areas']['smileys']['subsections']['editsmileys']['enabled'] = !empty(Config::$modSettings['smiley_enable']);
- $this->admin_areas['layout']['areas']['smileys']['subsections']['setorder']['enabled'] = !empty(Config::$modSettings['smiley_enable']);
- $this->admin_areas['layout']['areas']['smileys']['subsections']['editicons']['enabled'] = !empty(Config::$modSettings['messageIcons_enable']);
+ // Fill in the ID number for the current theme URL.
+ $this->admin_areas['config']['areas']['current_theme']['custom_url'] = sprintf($this->admin_areas['config']['areas']['current_theme']['custom_url'], Theme::$current->settings['theme_id']);
- if (empty(Config::$modSettings['spider_mode'])) {
- $this->admin_areas['layout']['areas']['sengines']['inactive'] = true;
- $this->admin_areas['layout']['areas']['sengines']['subsections'] = [];
- }
+ // Figure out what is enabled or not.
+ $this->admin_areas['forum']['areas']['adminlogoff']['enabled'] = empty(Config::$modSettings['securityDisable']);
- $this->admin_areas['members']['areas']['warnings']['inactive'] = Config::$modSettings['warning_settings'][0] == 0;
+ if (empty(Config::$modSettings['cal_enabled'])) {
+ $this->admin_areas['layout']['areas']['managecalendar']['inactive'] = true;
+ $this->admin_areas['layout']['areas']['managecalendar']['subsections'] = [];
+ }
- if (empty(Config::$modSettings['paid_enabled'])) {
- $this->admin_areas['members']['areas']['paidsubscribe']['inactive'] = true;
- $this->admin_areas['members']['areas']['paidsubscribe']['subsections'] = [];
- }
+ $this->admin_areas['layout']['areas']['smileys']['subsections']['addsmiley']['enabled'] = !empty(Config::$modSettings['smiley_enable']);
+ $this->admin_areas['layout']['areas']['smileys']['subsections']['editsmileys']['enabled'] = !empty(Config::$modSettings['smiley_enable']);
+ $this->admin_areas['layout']['areas']['smileys']['subsections']['setorder']['enabled'] = !empty(Config::$modSettings['smiley_enable']);
+ $this->admin_areas['layout']['areas']['smileys']['subsections']['editicons']['enabled'] = !empty(Config::$modSettings['messageIcons_enable']);
- $this->admin_areas['maintenance']['areas']['logs']['subsections']['errorlog']['enabled'] = !empty(Config::$modSettings['enableErrorLogging']);
- $this->admin_areas['maintenance']['areas']['logs']['subsections']['adminlog']['enabled'] = !empty(Config::$modSettings['adminlog_enabled']);
- $this->admin_areas['maintenance']['areas']['logs']['subsections']['modlog']['enabled'] = !empty(Config::$modSettings['modlog_enabled']);
- $this->admin_areas['maintenance']['areas']['logs']['subsections']['spiderlog']['enabled'] = !empty(Config::$modSettings['spider_mode']);
+ $this->admin_areas['layout']['areas']['managereactions']['subsections']['edit']['enabled'] = !empty(Config::$modSettings['enable_reacts']);
- // Give mods access to the menu.
- IntegrationHook::call('integrate_admin_areas', [&$this->admin_areas]);
- }
+ if (empty(Config::$modSettings['spider_mode'])) {
+ $this->admin_areas['layout']['areas']['sengines']['inactive'] = true;
+ $this->admin_areas['layout']['areas']['sengines']['subsections'] = [];
+ }
- /*************************
- * Internal static methods
- *************************/
+ $this->admin_areas['members']['areas']['warnings']['inactive'] = Config::$modSettings['warning_settings'][0] == 0;
- /**
- * Used by the adminLogin() method.
- *
- * If 'value' is an array, calls itself recursively.
- *
- * @param string $k The keys
- * @param string|array $v The values
- * @return string 'hidden' HTML form fields, containing key-value pairs
- */
- protected static function adminLogin_outputPostVars(string $k, string|array $v): string
- {
- if (!is_array($v)) {
- return "\n" . '
'"', '<' => '<', '>' => '>']) . '">';
- }
+ if (empty(Config::$modSettings['paid_enabled'])) {
+ $this->admin_areas['members']['areas']['paidsubscribe']['inactive'] = true;
+ $this->admin_areas['members']['areas']['paidsubscribe']['subsections'] = [];
+ }
- $ret = '';
+ $this->admin_areas['maintenance']['areas']['logs']['subsections']['errorlog']['enabled'] = !empty(Config::$modSettings['enableErrorLogging']);
+ $this->admin_areas['maintenance']['areas']['logs']['subsections']['adminlog']['enabled'] = !empty(Config::$modSettings['adminlog_enabled']);
+ $this->admin_areas['maintenance']['areas']['logs']['subsections']['modlog']['enabled'] = !empty(Config::$modSettings['modlog_enabled']);
+ $this->admin_areas['maintenance']['areas']['logs']['subsections']['spiderlog']['enabled'] = !empty(Config::$modSettings['spider_mode']);
- foreach ($v as $k2 => $v2) {
- $ret .= self::adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2);
+ // Give mods access to the menu.
+ IntegrationHook::call('integrate_admin_areas', [&$this->admin_areas]);
}
- return $ret;
- }
+ /*************************
+ * Internal static methods
+ *************************/
+
+ /**
+ * Used by the adminLogin() method.
+ *
+ * If 'value' is an array, calls itself recursively.
+ *
+ * @param string $k The keys
+ * @param string|array $v The values
+ * @return string 'hidden' HTML form fields, containing key-value pairs
+ */
+ protected static function adminLogin_outputPostVars(string $k, string|array $v): string
+ {
+ if (!is_array($v)) {
+ return "\n" . '
'"', '<' => '<', '>' => '>']) . '">';
+ }
- /**
- * Properly urlencodes a string to be used in a query.
- *
- * @param array $get A copy of $_GET.
- * @return string Our query string.
- */
- protected static function construct_query_string(array $get): string
- {
- $query_string = '';
+ $ret = '';
- // Awww, darn. The Config::$scripturl contains GET stuff!
- $q = strpos(Config::$scripturl, '?');
+ foreach ($v as $k2 => $v2) {
+ $ret .= self::adminLogin_outputPostVars($k . '[' . $k2 . ']', $v2);
+ }
- if ($q !== false) {
- parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr(Config::$scripturl, $q + 1), ';', '&')), $temp);
+ return $ret;
+ }
- foreach ($get as $k => $v) {
- // Only if it's not already in the Config::$scripturl!
- if (!isset($temp[$k])) {
- $query_string .= urlencode($k) . '=' . urlencode($v) . ';';
+ /**
+ * Properly urlencodes a string to be used in a query.
+ *
+ * @param array $get A copy of $_GET.
+ * @return string Our query string.
+ */
+ protected static function construct_query_string(array $get): string
+ {
+ $query_string = '';
+
+ // Awww, darn. The Config::$scripturl contains GET stuff!
+ $q = strpos(Config::$scripturl, '?');
+
+ if ($q !== false) {
+ parse_str(preg_replace('/&(\w+)(?=&|$)/', '&$1=', strtr(substr(Config::$scripturl, $q + 1), ';', '&')), $temp);
+
+ foreach ($get as $k => $v) {
+ // Only if it's not already in the Config::$scripturl!
+ if (!isset($temp[$k])) {
+ $query_string .= urlencode($k) . '=' . urlencode($v) . ';';
+ }
+ // If it changed, put it out there, but with an ampersand.
+ elseif ($temp[$k] != $get[$k]) {
+ $query_string .= urlencode($k) . '=' . urlencode($v) . '&';
+ }
}
- // If it changed, put it out there, but with an ampersand.
- elseif ($temp[$k] != $get[$k]) {
- $query_string .= urlencode($k) . '=' . urlencode($v) . '&';
+ } else {
+ // Add up all the data from $_GET into get_data.
+ foreach ($get as $k => $v) {
+ $query_string .= urlencode($k) . '=' . urlencode($v) . ';';
}
}
- } else {
- // Add up all the data from $_GET into get_data.
- foreach ($get as $k => $v) {
- $query_string .= urlencode($k) . '=' . urlencode($v) . ';';
- }
- }
- $query_string = substr($query_string, 0, -1);
+ $query_string = substr($query_string, 0, -1);
- return $query_string;
+ return $query_string;
+ }
}
-}
-?>
\ No newline at end of file
+ ?>
\ No newline at end of file
diff --git a/Sources/Actions/Admin/Features.php b/Sources/Actions/Admin/Features.php
index 4c6329c0c0..b39a73f0f2 100644
--- a/Sources/Actions/Admin/Features.php
+++ b/Sources/Actions/Admin/Features.php
@@ -73,7 +73,6 @@ class Features implements ActionInterface
'sig' => 'signature',
'profile' => 'profile',
'profileedit' => 'profileEdit',
- 'likes' => 'likes',
'mentions' => 'mentions',
'alerts' => 'alerts',
];
@@ -1415,32 +1414,6 @@ public function profileEdit(): void
SecurityToken::create('admin-ecp');
}
- /**
- * Handles modifying the likes settings.
- *
- * Accessed from ?action=admin;area=featuresettings;sa=likes
- */
- public function likes(): void
- {
- $config_vars = self::likesConfigVars();
-
- // Saving?
- if (isset($_GET['save'])) {
- User::$me->checkSession();
-
- IntegrationHook::call('integrate_save_likes_settings');
-
- ACP::saveDBSettings($config_vars);
- $_SESSION['adm-save'] = true;
- Utils::redirectexit('action=admin;area=featuresettings;sa=likes');
- }
-
- Utils::$context['post_url'] = Config::$scripturl . '?action=admin;area=featuresettings;save;sa=likes';
- Utils::$context['settings_title'] = Lang::$txt['likes'];
-
- ACP::prepareDBSettingContext($config_vars);
- }
-
/**
* Handles modifying the mentions settings.
*
@@ -1700,23 +1673,6 @@ public static function sigConfigVars(): array
return $config_vars;
}
- /**
- * Gets the configuration variables for the likes sub-action.
- *
- * @return array $config_vars for the likes sub-action.
- */
- public static function likesConfigVars(): array
- {
- $config_vars = [
- ['check', 'enable_likes'],
- ['permissions', 'likes_like'],
- ];
-
- IntegrationHook::call('integrate_likes_settings', [&$config_vars]);
-
- return $config_vars;
- }
-
/**
* Gets the configuration variables for the mentions sub-action.
*
@@ -1889,14 +1845,14 @@ public static function modifySignatureSettings(bool $return_config = false): ?ar
* @param bool $return_config Whether to return the config_vars array.
* @return ?array Returns nothing or returns the config_vars array.
*/
- public static function modifyLikesSettings($return_config = false): ?array
+ public static function modifyReactionsSettings($return_config = false): ?array
{
if (!empty($return_config)) {
- return self::likesConfigVars();
+ return self::reactionsConfigVars();
}
self::load();
- self::$obj->subaction = 'likes';
+ self::$obj->subaction = 'reactions';
self::$obj->execute();
return null;
diff --git a/Sources/Actions/Admin/Find.php b/Sources/Actions/Admin/Find.php
index 82dc01b803..c62e62abdd 100644
--- a/Sources/Actions/Admin/Find.php
+++ b/Sources/Actions/Admin/Find.php
@@ -89,6 +89,7 @@ class Find implements ActionInterface
[__NAMESPACE__ . '\\Server::exportConfigVars', 'area=serversettings;sa=export'],
[__NAMESPACE__ . '\\Server::loadBalancingConfigVars', 'area=serversettings;sa=loads'],
[__NAMESPACE__ . '\\Languages::getConfigVars', 'area=languages;sa=settings'],
+ [__NAMESPACE__ . '\\Reactions::getConfigVars', 'area=reactions;sa=settings'],
[__NAMESPACE__ . '\\Registration::getConfigVars', 'area=regcenter;sa=settings'],
[__NAMESPACE__ . '\\SearchEngines::getConfigVars', 'area=sengines;sa=settings'],
[__NAMESPACE__ . '\\Subscriptions::getConfigVars', 'area=paidsubscribe;sa=settings'],
diff --git a/Sources/Actions/Admin/Permissions.php b/Sources/Actions/Admin/Permissions.php
index 7a7be76d17..1edef07c66 100644
--- a/Sources/Actions/Admin/Permissions.php
+++ b/Sources/Actions/Admin/Permissions.php
@@ -129,7 +129,7 @@ class Permissions implements ActionInterface
'member_admin',
'profile',
'profile_account',
- 'likes',
+ 'reactions',
'mentions',
'bbc',
],
@@ -341,8 +341,8 @@ class Permissions implements ActionInterface
'group_level' => self::GROUP_LEVEL_MODERATOR,
'never_guests' => true,
],
- 'likes_like' => [
- 'view_group' => 'likes',
+ 'reactions_react' => [
+ 'view_group' => 'reactions',
'scope' => 'global',
'group_level' => self::GROUP_LEVEL_STANDARD,
'never_guests' => true,
@@ -1675,9 +1675,9 @@ public static function getPermissions(): array
self::$permissions['post_attachment']['hidden'] = true;
}
- // If likes are disabled, disable the related permission.
- if (empty(Config::$modSettings['enable_likes'])) {
- self::$permissions['likes_like']['hidden'] = true;
+ // If reactions are disabled, disable the related permission.
+ if (empty(Config::$modSettings['enable_reacts'])) {
+ self::$permissions['reactions_react']['hidden'] = true;
}
// If mentions are disabled, disable the related permission.
diff --git a/Sources/Actions/Admin/Reactions.php b/Sources/Actions/Admin/Reactions.php
new file mode 100644
index 0000000000..ddb06ac7ba
--- /dev/null
+++ b/Sources/Actions/Admin/Reactions.php
@@ -0,0 +1,384 @@
+ 'editreactions',
+ 'settings' => 'settings',
+ ];
+
+ /***********************
+ * Public methods
+ ***********************/
+
+ /**
+ * Handles modifying reactions settings
+ */
+ public static function settings(): void
+ {
+ $config_vars = self::getConfigVars();
+
+ // Setup the basics of the settings template.
+ Utils::$context['sub_template'] = 'show_settings';
+ Utils::$context['page_title'] = Lang::$txt['reactions_settings'];
+
+ if (isset($_REQUEST['save'])) {
+ User::$me->checkSession();
+ SecurityToken::validate('admin-mr');
+ IntegrationHook::call('integrate_save_reactions_settings');
+
+ // Yeppers, saving this...
+ ACP::saveDBSettings($config_vars);
+
+ $_SESSION['adm-save'] = true;
+ Utils::redirectexit('action=admin;area=managereactions;sa=settings');
+ }
+
+ // Finish up the form...
+ Utils::$context['post_url'] = Config::$scripturl . '?action=admin;area=managereactions;save;sa=settings';
+ Utils::$context['settings_title'] = Lang::$txt['reactions_settings'];
+
+ // We need this for the in-line permissions
+ SecurityToken::create('admin-mr');
+
+ ACP::prepareDBSettingContext($config_vars);
+ }
+
+ /***********************
+ * Public static methods
+ ***********************/
+
+ /**
+ * Gets the configuration variables for the settings sub-action.
+ *
+ * @return array $config_vars for the settings sub-action.
+ */
+ public static function getConfigVars(): array
+ {
+ $config_vars = [
+ ['check', 'enable_reacts'],
+ ['permissions', 'reactions_react'],
+ ];
+
+ IntegrationHook::call('integrate_reactions_settings', [&$config_vars]);
+
+ return $config_vars;
+ }
+
+ /**
+ * Dispatcher to whichever sub-action method is necessary.
+ */
+ public function execute(): void
+ {
+ $call = method_exists($this, self::$subactions[$this->subaction]) ? [$this, self::$subactions[$this->subaction]] : Utils::getCallable(self::$subactions[$this->subaction]);
+
+ if (!empty($call)) {
+ call_user_func($call);
+ }
+ }
+
+ /**
+ * Handle adding, deleting and editing reactions
+ */
+ public function editreactions(): void
+ {
+ // Make sure we select the right menu item
+ Menu::$loaded['admin']['currentsubsection'] = 'editreactions';
+
+ // Get the reactions. If we're updating things then we'll overwrite this later
+ $reactions = $this->getReactions();
+
+ // They must have submitted a form.
+ if (isset($_POST['reacts_save']) || isset($_POST['reacts_delete'])) {
+ User::$me->checkSession();
+ SecurityToken::validate('admin-mre');
+
+ // This will indicate whether we need to update the reactions cache later...
+ $do_update = false;
+
+ // Anything to delete?
+ if (isset($_POST['reacts_delete']) && isset($_POST['delete_reacts'])) {
+ $do_update = true;
+ $deleted = [];
+
+ foreach ($_POST['delete_reacts'] as $to_delete) {
+ $deleted[] = (int) $to_delete;
+ }
+
+ // Now to do the actual deleting
+ Db::$db->query('', '
+ DELETE FROM {db_prefix}reactions
+ WHERE id_reaction IN ({array_int:deleted})',
+ [
+ 'deleted' => $deleted,
+ ]
+ );
+
+ // Are there any posts that used these reactions?
+ $get_reacted_posts = Db::$db->query('', '
+ SELECT id_msg, COUNT (id_react) AS num_reacts
+ FROM {db_prefix}reactions
+ WHERE id_reaction IN ({array_int:deleted})
+ GROUP BY id_msg',
+ [
+ 'deleted' => $deleted,
+ ]
+ );
+
+ // Update the number of reactions for the affected post(s)
+ // Did we find anything?
+ if (Db::$db->num_rows($get_reacted_posts) > 0) {
+ while ($reacted_post = $get_reacted_posts->fetchAssoc()) {
+ Db::$db->query('', '
+ UPDATE {db_prefix}messages
+ SET reactions = reactions-{int:deleted}
+ WHERE id_msg = {int:msg}',
+ [
+ 'deleted' => $reacted_post['num_reacts'],
+ 'msg' => $reacted_post['id_msg'],
+ ]
+ );
+ }
+ }
+ } // Updating things?
+ elseif (isset($_POST['reacts'])) {
+ // Adding things?
+ if (isset($_POST['reacts_add'])) {
+ foreach ($_POST['reacts_add'] as $new_react) {
+ // No funny stuff now..
+ $new_react = trim($new_react);
+ if (!empty($new_react)) {
+ $add[] = [$new_react];
+ }
+ }
+
+ if (!empty($add)) {
+ $do_update = true;
+
+ // Insert the new reactions
+ Db::$db->insert('', '{db_prefix}reactions', ['name' => 'string'], $add, []);
+ }
+ }
+
+ // Updating things...
+ $updates = [];
+ foreach ($_POST['reacts'] as $id => $name) {
+ // Again, no funny stuff...
+ $name = trim($name);
+
+ // Did they update this one? Ignore empty ones for now
+ if ($reactions[$id] != $name && !empty($name)) {
+ $updates[] = [$id, $name];
+ }
+ }
+
+ // Anything to update?
+ if (!empty($updates)) {
+ $do_update = true;
+ // Do the update
+ Db::$db->insert('replace', '{db_prefix}reactions', ['id_reaction' => 'int', 'name' => 'string'], $updates, ['id_reaction']);
+ }
+ }
+
+ // If we updated anything, re-cache everything
+ if ($do_update) {
+ // Re-cache the reactions and update the reactions variable so the form will show the changes
+ CacheApi::put('reactions', null);
+ $reactions = $this->getReactions();
+ CacheApi::put('reactions', $reactions, 480);
+ }
+ }
+
+ // Set up the form now...
+
+ // Create our token
+ SecurityToken::create('admin-mre');
+
+ // Set up our list. Use a special function for the get_items so we can output things in input fields...
+ $listOptions = [
+ 'id' => 'reactions_list',
+ 'title' => Lang::$txt['reactions'],
+ 'no_items_label' => Lang::$txt['no_reactions'],
+ 'base_href' => Config::$scripturl . '?action=admin;area=managereactions;sa=edit',
+ 'get_items' => [
+ 'function' => function (int $start, int $items_per_page, string $sort_by) use ($reactions): array {
+ $items = [];
+ foreach ($reactions as $id => $name) {
+ $items[] = [
+ 'id' => $id,
+ 'name' => $name,
+ ];
+ }
+ return $items;
+ },
+ ],
+ 'get_count' => [
+ 'value' => count($reactions),
+ ],
+ 'columns' =>
+ [
+ 'name' =>
+ [
+ 'header' => [
+ 'value' => Lang::$txt['reactions_name'],
+ ],
+ 'data' => [
+ 'function' => function ($rowData) {
+ return '
';
+ }
+ ],
+ ],
+ 'check' => [
+ 'header' => [
+ 'value' => '
',
+ 'class' => 'centercol',
+ ],
+ 'data' => [
+ 'function' => function ($rowData) {
+ return '
';
+ },
+ 'class' => 'centercol',
+ ]
+ ]
+ ]
+ ];
+
+ // Add a row for a blank field to add a reaction, and a link to add another blank field.
+ $listOptions['additional_rows'] = [
+ [
+ 'position' => 'below_table_data',
+ 'value' => '
'
+ ],
+ [
+ // Clicking this magic button adds a new row...
+ 'position' => 'below_table_data',
+ 'value' => '
'
+ ],
+ [
+ // And last but not least our input buttons
+ 'position' => 'below_table_data',
+ 'value' => '
+
'
+ ]
+ ];
+
+ // And some inline JS to handle adding another row
+ $listOptions['javascript'] = '
+ function addrow() {
+ reacts_table = document.getElementById(\'reactions_list\');
+ new_row = document.getElementById(\'reactions_list\').insertRow(reacts_table.rows.length);
+ new_row.insertCell(0).innerHTML = \'
\';
+ new_row.insertCell(1).innerHTML = \'\';
+ }';
+
+ // Now that we have our list options set up, have some fun...
+ $listOptions['form'] = [
+ 'href' => Config::$scripturl . '?action=admin;area=managereactions;sa=edit;' . Utils::$context['session_var'] . '=' . Utils::$context['session_id'],
+ 'name' => 'list_reactions',
+ 'token' => 'admin-mre',
+ ];
+
+ new ItemList($listOptions);
+
+ Utils::$context['page_title'] = Lang::$txt['reactions_manage'];
+ Utils::$context['sub_template'] = 'show_list';
+ Utils::$context['default_list'] = 'reactions_list';
+ }
+
+ /******************
+ * Internal methods
+ ******************/
+
+ /**
+ * Constructor. Protected to force instantiation via self::load().
+ */
+ protected function __construct()
+ {
+ // Load up our language and set up the menu.
+ Lang::load('ManageReactions');
+
+ // Setup the admin tabs.
+ Menu::$loaded['admin']->tab_data = [
+ 'title' => Lang::$txt['reactions'],
+ 'help' => 'manage_reactions',
+ 'description' => Lang::$txt['admin_manage_reactions'],
+ 'tabs' => [
+ 'settings' => [
+ 'description' => Lang::$txt['reaction_settings_explain'],
+ ],
+ 'edit' => [
+ 'description' => Lang::$txt['manage_reactions_desc'],
+ 'disabled' => !Config::$modSettings['enable_reacts'],
+ ],
+ ],
+ ];
+
+ Utils::$context['last_tab'] = Config::$modSettings['enable_reacts'] ? 'edit' : 'settings';
+
+ if (!empty($_REQUEST['sa']) && isset(self::$subactions[$_REQUEST['sa']])) {
+ $this->subaction = $_REQUEST['sa'];
+ }
+
+ Utils::$context['sub_action'] = &$this->subaction;
+ }
+}
+?>
\ No newline at end of file
diff --git a/Sources/Actions/Display.php b/Sources/Actions/Display.php
index a916a4947a..2ce63a33c2 100644
--- a/Sources/Actions/Display.php
+++ b/Sources/Actions/Display.php
@@ -1144,7 +1144,7 @@ protected function getMessagesAndPosters(): void
}
/**
- * Initializes Msg::get() and loads attachments and likes.
+ * Initializes Msg::get() and loads attachments and reactions.
*/
protected function initDisplayContext(): void
{
@@ -1159,9 +1159,9 @@ protected function initDisplayContext(): void
Attachment::prepareByMsg($this->messages);
}
- // And the likes
- if (!empty(Config::$modSettings['enable_likes'])) {
- Utils::$context['my_likes'] = Topic::$info->getLikedMsgs();
+ // And the reactions
+ if (!empty(Config::$modSettings['enable_reacts'])) {
+ Utils::$context['my_reactions'] = Topic::$info->getReactedMsgs();
}
// Go to the last message if the given time is beyond the time of the last message.
@@ -1181,7 +1181,7 @@ protected function initDisplayContext(): void
Msg::$getter = [];
Utils::$context['first_message'] = 0;
Utils::$context['first_new_message'] = false;
- Utils::$context['likes'] = [];
+ Utils::$context['reactions'] = [];
}
// Set the callback. (do you REALIZE how much memory all the messages would take?!?)
diff --git a/Sources/Actions/Like.php b/Sources/Actions/React.php
similarity index 74%
rename from Sources/Actions/Like.php
rename to Sources/Actions/React.php
index cba745aa8d..64a04109bc 100644
--- a/Sources/Actions/Like.php
+++ b/Sources/Actions/React.php
@@ -23,6 +23,7 @@
use SMF\Db\DatabaseApi as Db;
use SMF\IntegrationHook;
use SMF\Lang;
+use SMF\ReactionTrait;
use SMF\Theme;
use SMF\Time;
use SMF\User;
@@ -31,9 +32,10 @@
/**
* Handles liking posts and displaying the list of who liked a post.
*/
-class Like implements ActionInterface
+class React implements ActionInterface
{
use ActionTrait;
+ use ReactionTrait;
/*******************
* Public properties
@@ -45,7 +47,7 @@ class Like implements ActionInterface
* The requested sub-action.
* This should be set by the constructor.
*/
- public string $subaction = 'like';
+ public string $subaction = 'react';
/**************************
* Public static properties
@@ -62,7 +64,7 @@ class Like implements ActionInterface
* regarding hooks, etc., assumes that they are only called by like().
*/
public static array $subactions = [
- 'like' => 'like',
+ 'react' => 'react',
'view' => 'view',
'delete' => 'delete',
'insert' => 'insert',
@@ -117,14 +119,14 @@ class Like implements ActionInterface
*
* The number of times the content has been liked.
*/
- protected int $num_likes = 0;
+ protected int $num_reacts = 0;
/**
* @var bool
*
* If the current user has already liked this content.
*/
- protected bool $already_liked = false;
+ protected bool $already_reacted = false;
/**
* @var array
@@ -132,15 +134,15 @@ class Like implements ActionInterface
* Mostly used for external integration. Needs to be filled as an array
* with the following keys:
*
- * 'can_like' bool|string True if the current user can actually like
+ * 'can_react' bool|string True if the current user can actually react to
* this content, or a Lang::$txt key for an
* error message if not.
*
- * 'redirect' string URL to redirect to after the like is submitted.
+ * 'redirect' string URL to redirect to after the react is submitted.
* If not set, will redirect to the forum index.
*
* 'type' string 6 character unique identifier for the content.
- * Must match what was sent in $_GET['ltype']
+ * Must match what was sent in $_GET['rtype']
*
* 'flush_cache' bool If true, reset the like content's cache entry
* after a new entry has been inserted. Optional.
@@ -153,8 +155,8 @@ class Like implements ActionInterface
* 'json' bool If true, the class will return a JSON object as
* a response instead of HTML. Default: false.
*/
- protected array $valid_likes = [
- 'can_like' => false,
+ protected array $valid_reacts = [
+ 'can_react' => false,
'redirect' => '',
'type' => '',
'flush_cache' => '',
@@ -163,7 +165,7 @@ class Like implements ActionInterface
];
/**
- * @var int
+ * @var int
*
* The topic ID. Used for liking messages.
*/
@@ -186,6 +188,18 @@ class Like implements ActionInterface
*/
protected mixed $data;
+ /**
+ * @var int
+ *
+ * The ID of the selected reaction. Should match an entry in the reactions table.
+ */
+ protected int $id_react = 0;
+
+ /** @var array
+ *
+ * An array of available reactions
+ */
+
/****************
* Public methods
****************/
@@ -264,10 +278,11 @@ protected function __construct()
$this->subaction = $_REQUEST['sa'];
}
- $this->type = $_GET['ltype'] ?? '';
- $this->content = (int) ($_GET['like'] ?? 0);
+ $this->type = $_GET['rtype'] ?? '';
+ $this->content = (int) ($_GET['react'] ?? 0);
$this->js = isset($_GET['js']);
$this->extra = $_GET['extra'] ?? false;
+ $this->id_react = $_GET['id_react'] ?? 0;
// We do not want to output debug information here.
if ($this->js) {
@@ -278,14 +293,14 @@ protected function __construct()
/**
* Performs basic checks on the data provided, checks for a valid msg like.
*
- * Calls integrate_valid_likes hook for retrieving all the data needed and
+ * Calls integrate_valid_reacts hook for retrieving all the data needed and
* apply checks based on the data provided.
*/
protected function check(): void
{
// This feature is currently disable.
- if (empty(Config::$modSettings['enable_likes'])) {
- $this->error = 'like_disable';
+ if (empty(Config::$modSettings['enable_reacts'])) {
+ $this->error = 'react_disable';
return;
}
@@ -301,6 +316,13 @@ protected function check(): void
return;
}
+ // Is this a valid reaction ID?
+ if ($this->id_react != 0 && in_array($this->id_react, $this->getReactions())) {
+ $this->error = 'invalid_reaction';
+
+ return;
+ }
+
// First we need to verify whether the user can see the type of content.
// This is set up to be extensible, so we'll check for the one type we
// do know about, and if it's not that, we'll defer to any hooks.
@@ -336,48 +358,48 @@ protected function check(): void
// So we know what topic it's in and more importantly we know the
// user can see it. If we're not viewing, we need some info set up.
- $this->valid_likes['type'] = 'msg';
- $this->valid_likes['flush_cache'] = 'likes_topic_' . $this->id_topic . '_' . User::$me->id;
- $this->valid_likes['redirect'] = 'topic=' . $this->id_topic . '.msg' . $this->content . '#msg' . $this->content;
+ $this->valid_reacts['type'] = 'msg';
+ $this->valid_reacts['flush_cache'] = 'reacts_topic_' . $this->id_topic . '_' . User::$me->id;
+ $this->valid_reacts['redirect'] = 'topic=' . $this->id_topic . '.msg' . $this->content . '#msg' . $this->content;
- $this->valid_likes['can_like'] = (User::$me->id == $topicOwner ? 'cannot_like_content' : (User::$me->allowedTo('likes_like') ? true : 'cannot_like_content'));
+ $this->valid_reacts['can_react'] = (User::$me->id == $topicOwner ? 'cannot_react_content' : (User::$me->allowedTo('reacts_react') ? true : 'cannot_react_content'));
} else {
/*
* MOD AUTHORS: This will give you whatever the user offers up in
- * terms of liking, e.g. $this->type=msg, $this->content=1.
+ * terms of reacting, e.g. $this->type=msg, $this->content=1.
*
* When you hook this, check $this->type first. If it is not
* something your mod worries about, return false.
*
* Otherwise, return an array according to the documentation for
- * $this->valid_likes. Determine (however you need to) that the user
+ * $this->valid_reacts. Determine (however you need to) that the user
* can see and can_like the relevant liked content (and it exists).
* Remember that users can't like their own content.
*
* If the user can like it, you MUST return your type in the 'type'
* key of the returned array.
*
- * See also issueLike() for further notes.
+ * See also issueReact() for further notes.
*/
- $can_like = IntegrationHook::call('integrate_valid_likes', [$this->type, $this->content, $this->subaction, $this->js, $this->extra]);
+ $can_react = IntegrationHook::call('integrate_valid_reacts', [$this->type, $this->content, $this->subaction, $this->js, $this->extra]);
$found = false;
- if (!empty($can_like)) {
- $can_like = (array) $can_like;
+ if (!empty($can_react)) {
+ $can_react = (array) $can_react;
- foreach ($can_like as $result) {
+ foreach ($can_react as $result) {
if ($result !== false) {
// Match the type with what we already have.
if (!isset($result['type']) || $result['type'] != $this->type) {
- $this->error = 'not_valid_like_type';
+ $this->error = 'not_valid_react_type';
return;
}
// Fill out the rest.
$this->type = $result['type'];
- $this->valid_likes = array_merge($this->valid_likes, $result);
+ $this->valid_reacts = array_merge($this->valid_reacts, $result);
$found = true;
break;
@@ -392,10 +414,10 @@ protected function check(): void
}
}
- // Is the user able to like this?
- // Viewing a list of likes doesn't require this permission.
- if ($this->subaction != 'view' && isset($this->valid_likes['can_like']) && is_string($this->valid_likes['can_like'])) {
- $this->error = $this->valid_likes['can_like'];
+ // Is the user able to react to this?
+ // Viewing a list of reactions doesn't require this permission.
+ if ($this->subaction != 'view' && isset($this->valid_reacts['can_react']) && is_string($this->valid_reacts['can_react'])) {
+ $this->error = $this->valid_reacts['can_react'];
return;
}
@@ -408,13 +430,13 @@ protected function delete(): void
{
Db::$db->query(
'',
- 'DELETE FROM {db_prefix}user_likes
- WHERE content_id = {int:like_content}
- AND content_type = {string:like_type}
+ 'DELETE FROM {db_prefix}user_reacts
+ WHERE content_id = {int:react_content}
+ AND content_type = {string:react_type}
AND id_member = {int:id_member}',
[
- 'like_content' => $this->content,
- 'like_type' => $this->type,
+ 'react_content' => $this->content,
+ 'react_type' => $this->type,
'id_member' => User::$me->id,
],
);
@@ -427,15 +449,15 @@ protected function delete(): void
// Check to see if there is an unread alert to delete as well...
Alert::deleteWhere(
[
- 'content_id = {int:like_content}',
- 'content_type = {string:like_type}',
+ 'content_id = {int:react_content}',
+ 'content_type = {string:react_type}',
'id_member_started = {int:id_member_started}',
'content_action = {string:content_action}',
'is_read = {int:unread}',
],
[
- 'like_content' => $this->content,
- 'like_type' => $this->type,
+ 'react_content' => $this->content,
+ 'react_type' => $this->type,
'id_member_started' => User::$me->id,
'content_action' => 'like',
'unread' => 0,
@@ -444,7 +466,7 @@ protected function delete(): void
}
/**
- * Inserts a new entry on user_likes table.
+ * Inserts a new entry on user_reacts table.
* Creates a background task for the inserted entry.
*/
protected function insert(): void
@@ -456,24 +478,27 @@ protected function insert(): void
$content = $this->content;
$user = (array) User::$me;
$time = time();
+ $id = $this->id_react;
- IntegrationHook::call('integrate_issue_like_before', [&$type, &$content, &$user, &$time]);
+ IntegrationHook::call('integrate_issue_react_before', [&$type, &$content, &$user, &$time, &$id]);
// Insert the like.
Db::$db->insert(
'insert',
- '{db_prefix}user_likes',
+ '{db_prefix}user_reacts',
[
'content_id' => 'int',
'content_type' => 'string-6',
'id_member' => 'int',
- 'like_time' => 'int',
+ 'react_time' => 'int',
+ 'id_react' => 'int',
],
[
$content,
$type,
$user['id'],
$time,
+ $id,
],
[
'content_id',
@@ -484,7 +509,7 @@ protected function insert(): void
// Add a background task to process sending alerts.
// MOD AUTHORS: you can add your own background task for your own custom
- // like event using the "integrate_issue_like" hook or your callback,
+ // react event using the "integrate_issue_react" hook or your callback,
// both are immediately called after this.
if ($this->type == 'msg') {
Db::$db->insert(
@@ -496,7 +521,7 @@ protected function insert(): void
'claimed_time' => 'int',
],
[
- 'SMF\\Tasks\\Likes_Notify',
+ 'SMF\\Tasks\\Reacts_Notify',
Utils::jsonEncode([
'content_id' => $content,
'content_type' => $type,
@@ -517,37 +542,37 @@ protected function insert(): void
}
/**
- * Sets $this->num_likes to the actual number of likes that the content has.
+ * Sets $this->num_reacts to the actual number of reactions that the content has.
*/
protected function count(): void
{
$request = Db::$db->query(
'',
'SELECT COUNT(*)
- FROM {db_prefix}user_likes
- WHERE content_id = {int:like_content}
- AND content_type = {string:like_type}',
+ FROM {db_prefix}user_reacts
+ WHERE content_id = {int:react_content}
+ AND content_type = {string:react_type}',
[
- 'like_content' => $this->content,
- 'like_type' => $this->type,
+ 'react_content' => $this->content,
+ 'react_type' => $this->type,
],
);
- list($likes) = Db::$db->fetch_row($request);
+ list($reacts) = Db::$db->fetch_row($request);
Db::$db->free_result($request);
- $this->num_likes = (int) $likes;
+ $this->num_reacts = (int) $reacts;
if ($this->subaction == __FUNCTION__) {
- $this->data = $this->num_likes;
+ $this->data = $this->num_reacts;
}
}
/**
- * Performs a like action, either like or unlike.
+ * Performs a reaction action, either react or "unreact"
*
- * Counts the total of likes and calls a hook after the event.
+ * Counts the total of reactions and calls a hook after the event.
*/
- protected function like(): void
+ protected function react(): void
{
// Safety first!
if (empty($this->type) || empty($this->content)) {
@@ -556,24 +581,24 @@ protected function like(): void
return;
}
- // Do we already like this?
+ // Did we already react to this?
$request = Db::$db->query(
'',
'SELECT content_id, content_type, id_member
- FROM {db_prefix}user_likes
- WHERE content_id = {int:like_content}
- AND content_type = {string:like_type}
+ FROM {db_prefix}user_reacts
+ WHERE content_id = {int:react_content}
+ AND content_type = {string:react_type}
AND id_member = {int:id_member}',
[
- 'like_content' => $this->content,
- 'like_type' => $this->type,
+ 'react_content' => $this->content,
+ 'react_type' => $this->type,
'id_member' => User::$me->id,
],
);
- $this->already_liked = Db::$db->num_rows($request) != 0;
+ $this->already_reacted = Db::$db->num_rows($request) != 0;
Db::$db->free_result($request);
- if ($this->already_liked) {
+ if ($this->already_reacted) {
$this->delete();
} else {
$this->insert();
@@ -588,104 +613,105 @@ protected function like(): void
Db::$db->query(
'',
'UPDATE {db_prefix}messages
- SET likes = {int:num_likes}
+ SET reacts = {int:num_reacts}
WHERE id_msg = {int:id_msg}',
[
'id_msg' => $this->content,
- 'num_likes' => $this->num_likes,
+ 'num_reacts' => $this->num_reacts,
],
);
}
// Any callbacks?
- elseif (!empty($this->valid_likes['callback'])) {
- $call = Utils::getCallable($this->valid_likes['callback']);
+ elseif (!empty($this->valid_reacts['callback'])) {
+ $call = Utils::getCallable($this->valid_reacts['callback']);
if (!empty($call)) {
call_user_func_array($call, [$this]);
}
}
- // Sometimes there might be other things that need updating after we do this like.
- IntegrationHook::call('integrate_issue_like', [$this]);
+ // Sometimes there might be other things that need updating after we do this reaction.
+ IntegrationHook::call('integrate_issue_react', [$this]);
// Now some clean up. This is provided here for any like handlers that
// want to do any cache flushing.
- // This way a like handler doesn't need to explicitly declare anything
- // in integrate_issue_like, but do so in integrate_valid_likes where it
+ // This way a reaction handler doesn't need to explicitly declare anything
+ // in integrate_issue_react, but do so in integrate_valid_reacts where it
// absolutely has to exist.
- if (!empty($this->valid_likes['flush_cache'])) {
- CacheApi::put($this->valid_likes['flush_cache'], null);
+ if (!empty($this->valid_reacts['flush_cache'])) {
+ CacheApi::put($this->valid_reacts['flush_cache'], null);
}
// All done, start building the data to pass as response.
$this->data = [
'id_topic' => !empty($this->id_topic) ? $this->id_topic : 0,
'id_content' => $this->content,
- 'count' => $this->num_likes,
- 'can_like' => $this->valid_likes['can_like'],
- 'already_liked' => empty($this->already_liked),
+ 'count' => $this->num_reacts,
+ 'can_react' => $this->valid_reacts['can_react'],
+ 'already_reacted' => empty($this->already_reacted),
'type' => $this->type,
];
}
/**
- * This is for viewing the people who liked a thing.
+ * This is for viewing the people who reacted to a thing.
*
* Accessed from index.php?action=likes;view and should generally load in a
* popup.
*
* We use a template for this in case themers want to style it.
+ * @TODO: Handle filtering by reaction
*/
protected function view(): void
{
// Firstly, load what we need. We already know we can see this, so that's something.
- Utils::$context['likers'] = [];
+ Utils::$context['reactors'] = [];
$request = Db::$db->query(
'',
- 'SELECT id_member, like_time
- FROM {db_prefix}user_likes
- WHERE content_id = {int:like_content}
- AND content_type = {string:like_type}
- ORDER BY like_time DESC',
+ 'SELECT id_member, react_time, id_react
+ FROM {db_prefix}user_reacts
+ WHERE content_id = {int:react_content}
+ AND content_type = {string:react_type}
+ ORDER BY react_time DESC',
[
- 'like_content' => $this->content,
- 'like_type' => $this->type,
+ 'react_content' => $this->content,
+ 'react_type' => $this->type,
],
);
while ($row = Db::$db->fetch_assoc($request)) {
- Utils::$context['likers'][$row['id_member']] = ['timestamp' => $row['like_time']];
+ Utils::$context['reactors'][$row['id_member']] = ['timestamp' => $row['react_time'], 'id_react' => $row['id_react']];
}
Db::$db->free_result($request);
// Now to get member data, including avatars and so on.
- $members = array_keys(Utils::$context['likers']);
+ $members = array_keys(Utils::$context['reactors']);
$loaded = User::load($members);
if (count($loaded) != count($members)) {
$members = array_diff($members, array_map(fn ($member) => $member->id, $loaded));
foreach ($members as $not_loaded) {
- unset(Utils::$context['likers'][$not_loaded]);
+ unset(Utils::$context['reactors'][$not_loaded]);
}
}
- foreach (Utils::$context['likers'] as $liker => $dummy) {
- if (!isset(User::$loaded[$liker])) {
- unset(Utils::$context['likers'][$liker]);
+ foreach (Utils::$context['reactors'] as $reactor => $dummy) {
+ if (!isset(User::$loaded[$reactor])) {
+ unset(Utils::$context['reactors'][$reactor]);
continue;
}
- Utils::$context['likers'][$liker]['profile'] = User::$loaded[$liker]->format();
- Utils::$context['likers'][$liker]['time'] = !empty($dummy['timestamp']) ? Time::create('@' . $dummy['timestamp'])->format() : '';
+ Utils::$context['reactors'][$reactor]['profile'] = User::$loaded[$reactor]->format();
+ Utils::$context['reactors'][$reactor]['time'] = !empty($dummy['timestamp']) ? Time::create('@' . $dummy['timestamp'])->format() : '';
}
- Utils::$context['page_title'] = strip_tags(Lang::getTxt('likes_count', ['num' => count(Utils::$context['likers'])]));
+ Utils::$context['page_title'] = strip_tags(Lang::getTxt('reacts_count', ['num' => count(Utils::$context['reactors'])]));
// Lastly, setting up for display.
- Theme::loadTemplate('Likes');
+ Theme::loadTemplate('Reacts');
Lang::load('Help'); // For the close window button.
Utils::$context['template_layers'] = [];
Utils::$context['sub_template'] = 'popup';
@@ -707,42 +733,42 @@ protected function respond(): void
}
// Want a JSON response, do they?
- if ($this->valid_likes['json']) {
+ if ($this->valid_reacts['json']) {
$this->sendJsonReponse();
return;
}
// Set everything up for display.
- Theme::loadTemplate('Likes');
+ Theme::loadTemplate('Reacts');
Utils::$context['template_layers'] = [];
// If there are any errors, process them first.
if ($this->error) {
// If this is a generic error, set it up good.
if ($this->error == 'cannot_') {
- $this->error = $this->subaction == 'view' ? 'cannot_view_likes' : 'cannot_like_content';
+ $this->error = $this->subaction == 'view' ? 'cannot_view_reacts' : 'cannot_react_content';
}
// Is this request coming from an AJAX call?
if ($this->js) {
Utils::$context['sub_template'] = 'generic';
- Utils::$context['data'] = Lang::$txt[$this->error] ?? Lang::$txt['like_error'];
+ Utils::$context['data'] = Lang::$txt[$this->error] ?? Lang::$txt['react_error'];
}
// Nope? Then just do a redirect to whatever URL was provided.
else {
- Utils::redirectexit(!empty($this->valid_likes['redirect']) ? $this->valid_likes['redirect'] . ';error=' . $this->error : '');
+ Utils::redirectexit(!empty($this->valid_reacts['redirect']) ? $this->valid_reacts['redirect'] . ';error=' . $this->error : '');
}
return;
}
- // A like operation.
+ // A react operation.
// Not an AJAX request so send the user back to the previous
// location or the main page.
if (!$this->js) {
- Utils::redirectexit(!empty($this->valid_likes['redirect']) ? $this->valid_likes['redirect'] : '');
+ Utils::redirectexit(!empty($this->valid_reacts['redirect']) ? $this->valid_reacts['redirect'] : '');
}
// These fine gentlemen all share the same template.
@@ -750,7 +776,7 @@ protected function respond(): void
if (in_array($this->subaction, $generic)) {
Utils::$context['sub_template'] = 'generic';
- Utils::$context['data'] = Lang::$txt['like_' . $this->data] ?? $this->data;
+ Utils::$context['data'] = Lang::$txt['react_' . $this->data] ?? $this->data;
}
// Directly pass the current called sub-action and the data
// generated by its associated Method.
@@ -772,14 +798,14 @@ protected function sendJsonReponse(): void
// If there is an error, send it.
if ($this->error) {
if ($this->error == 'cannot_') {
- $this->error = $this->subaction == 'view' ? 'cannot_view_likes' : 'cannot_like_content';
+ $this->error = $this->subaction == 'view' ? 'cannot_view_reacts' : 'cannot_react_content';
}
$print['error'] = $this->error;
}
// Do you want to add something at the very last minute?
- IntegrationHook::call('integrate_likes_json_response', [&$print]);
+ IntegrationHook::call('integrate_reacts_json_response', [&$print]);
// Print the data.
Utils::serverResponse(Utils::jsonEncode($print));
diff --git a/Sources/Actions/Stats.php b/Sources/Actions/Stats.php
index 11a5d13b2c..39aece123c 100644
--- a/Sources/Actions/Stats.php
+++ b/Sources/Actions/Stats.php
@@ -608,26 +608,26 @@ public function execute(): void
CacheApi::put('stats_total_time_members', $temp2, 480);
}
- // Likes.
- if (!empty(Config::$modSettings['enable_likes'])) {
+ // Reactions.
+ if (!empty(Config::$modSettings['enable_reacts'])) {
// Liked messages top 10.
- Utils::$context['stats_blocks']['liked_messages'] = [];
- $max_liked_message = 1;
- $liked_messages = Db::$db->query(
+ Utils::$context['stats_blocks']['reacted_messages'] = [];
+ $max_reacted_message = 1;
+ $reacted_messages = Db::$db->query(
'',
- 'SELECT m.id_msg, m.subject, m.likes, m.id_board, m.id_topic, t.approved
+ 'SELECT m.id_msg, m.subject, m.reacts, m.id_board, m.id_topic, t.approved
FROM (
- SELECT n.id_msg, n.subject, n.likes, n.id_board, n.id_topic
+ SELECT n.id_msg, n.subject, n.reacts, n.id_board, n.id_topic
FROM {db_prefix}messages as n
- ORDER BY n.likes DESC
+ ORDER BY n.reacts DESC
LIMIT 1000
) AS m
INNER JOIN {db_prefix}topics AS t ON (m.id_topic = t.id_topic)
INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board' . (!empty(Config::$modSettings['recycle_enable']) && Config::$modSettings['recycle_board'] > 0 ? '
AND b.id_board != {int:recycle_board}' : '') . ')
- WHERE m.likes > 0 AND {query_see_board}' . (Config::$modSettings['postmod_active'] ? '
+ WHERE m.reacts > 0 AND {query_see_board}' . (Config::$modSettings['postmod_active'] ? '
AND t.approved = {int:is_approved}' : '') . '
- ORDER BY m.likes DESC
+ ORDER BY m.reacts DESC
LIMIT 10',
[
'recycle_board' => Config::$modSettings['recycle_board'],
@@ -635,35 +635,35 @@ public function execute(): void
],
);
- while ($row_liked_message = Db::$db->fetch_assoc($liked_messages)) {
- Lang::censorText($row_liked_message['subject']);
+ while ($row_reacted_message = Db::$db->fetch_assoc($reacted_messages)) {
+ Lang::censorText($row_reacted_message['subject']);
- Utils::$context['stats_blocks']['liked_messages'][] = [
- 'id' => $row_liked_message['id_topic'],
- 'subject' => $row_liked_message['subject'],
- 'num' => $row_liked_message['likes'],
- 'href' => Config::$scripturl . '?msg=' . $row_liked_message['id_msg'],
- 'link' => '
' . $row_liked_message['subject'] . '',
+ Utils::$context['stats_blocks']['reacted_messages'][] = [
+ 'id' => $row_reacted_message['id_topic'],
+ 'subject' => $row_reacted_message['subject'],
+ 'num' => $row_reacted_message['reacts'],
+ 'href' => Config::$scripturl . '?msg=' . $row_reacted_message['id_msg'],
+ 'link' => '
' . $row_reacted_message['subject'] . '',
];
- if ($max_liked_message < $row_liked_message['likes']) {
- $max_liked_message = $row_liked_message['likes'];
+ if ($max_reacted_message < $row_reacted_message['reacts']) {
+ $max_reacted_message = $row_reacted_message['reacts'];
}
}
- Db::$db->free_result($liked_messages);
+ Db::$db->free_result($reacted_messages);
- foreach (Utils::$context['stats_blocks']['liked_messages'] as $i => $liked_messages) {
- Utils::$context['stats_blocks']['liked_messages'][$i]['percent'] = round(($liked_messages['num'] * 100) / $max_liked_message);
+ foreach (Utils::$context['stats_blocks']['reacted_messages'] as $i => $reacted_messages) {
+ Utils::$context['stats_blocks']['reacted_messages'][$i]['percent'] = round(($reacted_messages['num'] * 100) / $max_reacted_message);
}
- // Liked users top 10.
- Utils::$context['stats_blocks']['liked_users'] = [];
- $max_liked_users = 1;
- $liked_users = Db::$db->query(
+ // Reacted users top 10.
+ Utils::$context['stats_blocks']['reacted_users'] = [];
+ $max_reacted_users = 1;
+ $reacted_users = Db::$db->query(
'',
- 'SELECT m.id_member AS liked_user, COUNT(l.content_id) AS count, mem.real_name
- FROM {db_prefix}user_likes AS l
- INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
+ 'SELECT m.id_member AS reacted_user, COUNT(r.content_id) AS count, mem.real_name
+ FROM {db_prefix}user_reacts AS r
+ INNER JOIN {db_prefix}messages AS m ON (r.content_id = m.id_msg)
INNER JOIN {db_prefix}members AS mem ON (m.id_member = mem.id_member)
WHERE content_type = {literal:msg}
AND m.id_member > {int:zero}
@@ -676,24 +676,24 @@ public function execute(): void
],
);
- while ($row_liked_users = Db::$db->fetch_assoc($liked_users)) {
- Utils::$context['stats_blocks']['liked_users'][] = [
- 'id' => $row_liked_users['liked_user'],
- 'num' => $row_liked_users['count'],
- 'href' => Config::$scripturl . '?action=profile;u=' . $row_liked_users['liked_user'],
- 'name' => $row_liked_users['real_name'],
- 'link' => '
' . $row_liked_users['real_name'] . '',
+ while ($row_reacted_users = Db::$db->fetch_assoc($reacted_users)) {
+ Utils::$context['stats_blocks']['reacted_users'][] = [
+ 'id' => $row_reacted_users['reacted_user'],
+ 'num' => $row_reacted_users['count'],
+ 'href' => Config::$scripturl . '?action=profile;u=' . $row_reacted_users['reacted_user'],
+ 'name' => $row_reacted_users['real_name'],
+ 'link' => '
' . $row_reacted_users['real_name'] . '',
];
- if ($max_liked_users < $row_liked_users['count']) {
- $max_liked_users = $row_liked_users['count'];
+ if ($max_reacted_users < $row_reacted_users['count']) {
+ $max_reacted_users = $row_reacted_users['count'];
}
}
- Db::$db->free_result($liked_users);
+ Db::$db->free_result($reacted_users);
- foreach (Utils::$context['stats_blocks']['liked_users'] as $i => $liked_users) {
- Utils::$context['stats_blocks']['liked_users'][$i]['percent'] = round(($liked_users['num'] * 100) / $max_liked_users);
+ foreach (Utils::$context['stats_blocks']['reacted_users'] as $i => $reacted_users) {
+ Utils::$context['stats_blocks']['reacted_users'][$i]['percent'] = round(($reacted_users['num'] * 100) / $max_reacted_users);
}
}
diff --git a/Sources/Forum.php b/Sources/Forum.php
index d3b8a70d00..4b9812ee68 100644
--- a/Sources/Forum.php
+++ b/Sources/Forum.php
@@ -68,7 +68,7 @@ class Forum
'helpadmin' => ['', 'SMF\\Actions\\HelpAdmin::call'],
'jsmodify' => ['', 'SMF\\Actions\\JavaScriptModify::call'],
'jsoption' => ['', 'SMF\\Theme::setJavaScript'],
- 'likes' => ['', 'SMF\\Actions\\Like::call'],
+ 'likes' => ['', 'SMF\\Actions\\React::call'],
'lock' => ['', 'SMF\\Topic::lock'],
'lockvoting' => ['', 'SMF\\Poll::lock'],
'login' => ['', 'SMF\\Actions\\Login::call'],
@@ -112,7 +112,7 @@ class Forum
'sticky' => ['', 'SMF\\Topic::sticky'],
'theme' => ['', 'SMF\\Theme::dispatch'],
'trackip' => ['', 'SMF\\Actions\\TrackIP::call'],
- 'about:unknown' => ['', 'SMF\\Actions\\Like::BookOfUnknown'],
+ 'about:unknown' => ['', 'SMF\\Actions\\React::BookOfUnknown'],
'unread' => ['', 'SMF\\Actions\\Unread::call'],
'unreadreplies' => ['', 'SMF\\Actions\\UnreadReplies::call'],
'uploadAttach' => ['', 'SMF\\Actions\\AttachmentUpload::call'],
diff --git a/Sources/Msg.php b/Sources/Msg.php
index 0c2bf33e76..4c1cc0f8ac 100644
--- a/Sources/Msg.php
+++ b/Sources/Msg.php
@@ -160,9 +160,9 @@ class Msg implements \ArrayAccess
/**
* @var int
*
- * The number of likes this message has received.
+ * The number of reactions this message has received.
*/
- public int $likes = 0;
+ public int $reactions = 0;
/**
* @var bool
@@ -206,6 +206,13 @@ class Msg implements \ArrayAccess
*/
public static $getter;
+ /**
+ * @var array
+ *
+ * Variable to hold info about how many of each reaction we have
+ */
+ public static $reacts_count = [];
+
/*********************
* Internal properties
*********************/
@@ -309,6 +316,7 @@ public function format(int $counter = 0, array $format_options = []): array
'new' => empty($this->is_read),
'first_new' => isset(Utils::$context['start_from']) && Utils::$context['start_from'] == $counter,
'is_ignored' => !empty(Config::$modSettings['enable_buddylist']) && !empty(Theme::$current->options['posts_apply_ignore_list']) && in_array($this->id_member, User::$me->ignoreusers),
+ 'num_reactions' => (int) $this->reactions,
];
// Are we showing the icon?
@@ -468,15 +476,38 @@ public function format(int $counter = 0, array $format_options = []): array
$this->formatted['short_subject'] = Utils::shorten($this->formatted['subject'], $format_options['shorten_subject']);
}
- // Are likes enabled?
- if (!empty(Config::$modSettings['enable_likes'])) {
- $this->formatted['likes'] = [
- 'count' => $this->likes,
- 'you' => in_array($this->id, Utils::$context['my_likes'] ?? []),
- ];
+ // Are reactions enabled?
+ if (!empty(Config::$modSettings['enable_reacts'])) {
+ $this->formatted['reacts'] = [
+ 'count' => $this->reactions,
+ 'you' => in_array($this->id, Utils::$context['my_reactions'] ?? []),
+ ];;
+
+ if ($this->reactions != 0) {
+ // Load up the number of each type of reactions
+ $query = Db::$db->query(
+ '',
+ 'SELECT id_react, COUNT(*) AS num_reacts
+ FROM {db_prefix}user_reacts
+ WHERE content_type = {string:content_type}
+ AND content_id = {int:content_id}
+ GROUP BY id_react
+ ORDER BY num_reacts DESC',
+ [
+ 'content_type' => 'msg',
+ 'content_id' => $this->id,
+ ]
+ );
+
+ // Loop through the results
+ while ($row = Db::$db->fetchAssoc($query)) {
+ $this->reactions[$row['id_react']] = $row['num_reacts'];
+ }
+ Db::$db->freeResult($query);
+ }
if ($format_options['do_permissions']) {
- $this->formatted['likes']['can_like'] = !User::$me->is_guest && $this->id_member != User::$me->id && !empty($topic->permissions['can_like']);
+ $this->formatted['reactions']['can_react'] = !User::$me->is_guest && $this->id_member != User::$me->id && !empty($topic->permissions['can_react']);
}
}
@@ -2763,6 +2794,17 @@ public static function remove(int $message, bool $decreasePostCount = true): boo
],
);
+ // Drop any reactions related to this post. We can recalculate stats later
+ Db::$db->query(
+ '',
+ 'DELETE FROM {db_prefix}user_reacts
+ WHERE content_type = {string:msg} AND content_id = {int:id_msg}',
+ [
+ 'msg' => 'msg',
+ 'id_msg' => $message,
+ ],
+ );
+
// Delete attachment(s) if they exist.
$attachmentQuery = [
'attachment_type' => 0,
diff --git a/Sources/ReactionTrait.php b/Sources/ReactionTrait.php
new file mode 100644
index 0000000000..485ae78a7f
--- /dev/null
+++ b/Sources/ReactionTrait.php
@@ -0,0 +1,50 @@
+query(
+ '',
+ 'SELECT * FROM {db_prefix}reactions',
+ []);
+
+ while ($result = Db::$db->fetch_assoc($request)) {
+ $reactions[$result['id_reaction']] = $result['name'];
+ }
+
+ Db::$db->free_result($request);
+
+ // Cache the results
+ CacheApi::put('reactions', $reactions, 480);
+ }
+ return $reactions;
+ }
+}
\ No newline at end of file
diff --git a/Sources/Topic.php b/Sources/Topic.php
index 2c3e64242d..ff8a31a397 100644
--- a/Sources/Topic.php
+++ b/Sources/Topic.php
@@ -495,30 +495,30 @@ public function getNotificationPrefs(): array
}
/**
- * Gets the IDs of messages in this topic that the current user likes.
+ * Gets the IDs of messages in this topic that the current user reacted to
+ * as well as the ID of the chosen reaction for each message.
*
- * @param int $topic The topic ID to fetch the info from.
- * @return array IDs of messages in this topic that the current user likes.
+ * @return array An array of arrays each containing the ID of a reacted post and ID of the reaction
*/
- public function getLikedMsgs(): array
+ public function getReactedMsgs(): array
{
if (User::$me->is_guest) {
return [];
}
- $cache_key = 'likes_topic_' . $this->id . '_' . User::$me->id;
+ $cache_key = 'reacts_topic_' . $this->id . '_' . User::$me->id;
$ttl = 180;
- if (($liked_messages = CacheApi::get($cache_key, $ttl)) === null) {
- $liked_messages = [];
+ if (($reacted_messages = CacheApi::get($cache_key, $ttl)) === null) {
+ $reacted_messages = [];
$request = Db::$db->query(
'',
- 'SELECT content_id
- FROM {db_prefix}user_likes AS l
- INNER JOIN {db_prefix}messages AS m ON (l.content_id = m.id_msg)
- WHERE l.id_member = {int:current_user}
- AND l.content_type = {literal:msg}
+ 'SELECT r.content_id, r.id_reaction
+ FROM {db_prefix}user_reacts AS r
+ INNER JOIN {db_prefix}messages AS m ON (r.content_id = m.id_msg)
+ WHERE r.id_member = {int:current_user}
+ AND r.content_type = {literal:msg}
AND m.id_topic = {int:topic}',
[
'current_user' => User::$me->id,
@@ -527,14 +527,14 @@ public function getLikedMsgs(): array
);
while ($row = Db::$db->fetch_assoc($request)) {
- $liked_messages[] = (int) $row['content_id'];
+ $reacted_messages[] = [(int) $row['content_id'], (int) $row['id_reaction']];
}
Db::$db->free_result($request);
- CacheApi::put($cache_key, $liked_messages, $ttl);
+ CacheApi::put($cache_key, $reacted_messages, $ttl);
}
- return $liked_messages;
+ return $reacted_messages;
}
/**
@@ -1389,6 +1389,21 @@ public static function remove(array|int $topics, bool $decreasePostCount = true,
Attachment::remove($attachmentQuery, 'messages');
// Delete anything related to the topic.
+ // Do this first because we need the message IDs...
+ Db::$db->query(
+ '',
+ 'DELETE FROM {db_prefix}user_reacts
+ WHERE content_type={string:msg}
+ AND content_id IN
+ (SELECT id_msg
+ FROM {db_prefix}messages
+ WHERE id_topic IN ({array_int:topics})
+ )',
+ [
+ 'msg' => 'msg',
+ 'topics' => $topics,
+ ]
+ );
Db::$db->query(
'',
'DELETE FROM {db_prefix}messages
@@ -1462,9 +1477,9 @@ public static function remove(array|int $topics, bool $decreasePostCount = true,
* @param int $topic The topic ID to fetch the info from.
* @return array An array of IDs of messages in the specified topic that the current user likes
*/
- public static function prepareLikesContext(int $topic): array
+ public static function prepareReactsContext(int $topic): array
{
- return self::load($topic)->getLikedMsgs();
+ return self::load($topic)->getReactedMsgs();
}
/******************
diff --git a/Sources/User.php b/Sources/User.php
index b5652afc60..bd9bd8bcf1 100644
--- a/Sources/User.php
+++ b/Sources/User.php
@@ -3365,10 +3365,10 @@ public static function delete(int|array $users, bool $check_not_admin = false):
],
);
- // Delete anything they liked.
+ // Delete anything they reacted to.
Db::$db->query(
'',
- 'DELETE FROM {db_prefix}user_likes
+ 'DELETE FROM {db_prefix}user_reacts
WHERE id_member IN ({array_int:users})',
[
'users' => $users,
diff --git a/Themes/default/Likes.template.php b/Themes/default/Reacts.template.php
similarity index 97%
rename from Themes/default/Likes.template.php
rename to Themes/default/Reacts.template.php
index 5011ff1fb2..4fb474b5b0 100644
--- a/Themes/default/Likes.template.php
+++ b/Themes/default/Reacts.template.php
@@ -57,12 +57,12 @@ function template_popup()
/**
* Display a like button and info about how many people liked something
*/
-function template_like()
+function template_react()
{
echo '
';
- if (!empty(Utils::$context['data']['can_like']))
+ if (!empty(Utils::$context['data']['can_react']))
echo '
-
', Utils::$context['data']['already_liked'] ? Lang::$txt['unlike'] : Lang::$txt['like'], '
diff --git a/other/install_3-0_MySQL.sql b/other/install_3-0_MySQL.sql
index fe85f30832..7679195367 100644
--- a/other/install_3-0_MySQL.sql
+++ b/other/install_3-0_MySQL.sql
@@ -807,8 +807,8 @@ CREATE TABLE {$db_prefix}messages (
body TEXT NOT NULL,
icon VARCHAR(16) NOT NULL DEFAULT 'xx',
approved TINYINT NOT NULL DEFAULT '1',
- likes SMALLINT UNSIGNED NOT NULL DEFAULT '0',
version VARCHAR(5) NOT NULL DEFAULT '',
+ reactions SMALLINT UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (id_msg),
UNIQUE idx_id_board (id_board, id_msg, approved),
UNIQUE idx_id_member (id_member, id_msg),
@@ -818,7 +818,7 @@ CREATE TABLE {$db_prefix}messages (
INDEX idx_id_member_msg (id_member, approved, id_msg),
INDEX idx_current_topic (id_topic, id_msg, id_member, approved),
INDEX idx_related_ip (id_member, poster_ip, id_msg),
- INDEX idx_likes (likes)
+ INDEX idx_reactions (reactions)
) ENGINE={$engine};
#
@@ -992,6 +992,16 @@ CREATE TABLE {$db_prefix}qanda (
INDEX idx_lngfile (lngfile)
) ENGINE={$engine};
+#
+# Table structure for table `reactions`
+#
+
+CREATE TABLE {$db_prefix}reactions (
+ id_reaction SMALLINT UNSIGNED AUTO_INCREMENT,
+ name VARCHAR(255) NOT NULL DEFAULT '',
+ PRIMARY KEY (id_reaction)
+) ENGINE={$engine};
+
#
# Table structure for table `scheduled_tasks`
#
@@ -1192,17 +1202,18 @@ CREATE TABLE {$db_prefix}user_drafts (
) ENGINE={$engine};
#
-# Table structure for table `user_likes`
+# Table structure for table `user_reacts`
#
-CREATE TABLE {$db_prefix}user_likes (
+CREATE TABLE {$db_prefix}user_reacts (
id_member MEDIUMINT UNSIGNED DEFAULT '0',
+ id_react SMALLINT UNSIGNED DEFAULT '0',
content_type CHAR(6) DEFAULT '',
content_id INT UNSIGNED DEFAULT '0',
- like_time INT UNSIGNED NOT NULL DEFAULT '0',
+ react_time INT UNSIGNED NOT NULL DEFAULT '0',
PRIMARY KEY (content_id, content_type, id_member),
INDEX content (content_id, content_type),
- INDEX liker (id_member)
+ INDEX reactor (id_member)
) ENGINE={$engine};
#
@@ -1751,6 +1762,15 @@ VALUES (-1, 'search_posts'),
(2, 'access_mod_center');
# --------------------------------------------------------
+#
+# Dumping data for table `reactions`
+#
+
+INSERT INTO {$db_prefix}reactions
+ (id_reaction, name)
+VALUES
+ (1, 'like');
+
#
# Dumping data for table `scheduled_tasks`
#
@@ -2098,7 +2118,7 @@ VALUES (0, 'alert_timeout', 10),
(0, 'member_report', 3),
(0, 'member_report_reply', 3),
(0, 'msg_auto_notify', 0),
- (0, 'msg_like', 1),
+ (0, 'msg_react', 1),
(0, 'msg_mention', 1),
(0, 'msg_notify_pref', 1),
(0, 'msg_notify_type', 1),
diff --git a/other/install_3-0_PostgreSQL.sql b/other/install_3-0_PostgreSQL.sql
index a079b2e323..1c0fc462f5 100644
--- a/other/install_3-0_PostgreSQL.sql
+++ b/other/install_3-0_PostgreSQL.sql
@@ -1201,7 +1201,7 @@ CREATE TABLE {$db_prefix}messages (
body text NOT NULL,
icon varchar(16) NOT NULL DEFAULT 'xx',
approved smallint NOT NULL DEFAULT '1',
- likes smallint NOT NULL DEFAULT '0',
+ reactions smallint NOT NULL DEFAULT '0',
version varchar(5) NOT NULL DEFAULT '',
PRIMARY KEY (id_msg)
);
@@ -1218,7 +1218,7 @@ CREATE INDEX {$db_prefix}messages_show_posts ON {$db_prefix}messages (id_member,
CREATE INDEX {$db_prefix}messages_id_member_msg ON {$db_prefix}messages (id_member, approved, id_msg);
CREATE INDEX {$db_prefix}messages_current_topic ON {$db_prefix}messages (id_topic, id_msg, id_member, approved);
CREATE INDEX {$db_prefix}messages_related_ip ON {$db_prefix}messages (id_member, poster_ip, id_msg);
-CREATE INDEX {$db_prefix}messages_likes ON {$db_prefix}messages (likes);
+CREATE INDEX {$db_prefix}messages_reactions ON {$db_prefix}messages (reactions);
#
# Table structure for table `moderators`
#
@@ -1455,6 +1455,22 @@ CREATE TABLE {$db_prefix}qanda (
CREATE INDEX {$db_prefix}qanda_lngfile ON {$db_prefix}qanda (lngfile varchar_pattern_ops);
+#
+# Sequence for table `reactions`
+#
+
+CREATE SEQUENCE {$db_prefix}reactions_seq START WITH 0;
+
+#
+# Table structure for table `reactions`
+#
+
+CREATE TABLE {$db_prefix}reactions (
+ id_reaction smallint DEFAULT nextval('{$db_prefix}reactions_seq'),
+ name varchar(255) NOT NULL DEFAULT '',
+ PRIMARY KEY (id_reaction)
+);
+
#
# Sequence for table `scheduled_tasks`
#
@@ -1727,23 +1743,24 @@ CREATE TABLE {$db_prefix}user_drafts (
CREATE UNIQUE INDEX {$db_prefix}user_drafts_id_member ON {$db_prefix}user_drafts (id_member, id_draft, type);
#
-# Table structure for table `user_likes`
+# Table structure for table `user_reacts`
#
-CREATE TABLE {$db_prefix}user_likes (
+CREATE TABLE {$db_prefix}user_reacts (
id_member int NOT NULL DEFAULT '0',
+ id_reaction smallint NOT NULL DEFAULT '0',
content_type char(6) DEFAULT '',
content_id int NOT NULL DEFAULT '0',
- like_time int NOT NULL DEFAULT '0',
+ react_time int NOT NULL DEFAULT '0',
PRIMARY KEY (content_id, content_type, id_member)
);
#
-# Indexes for table `user_likes`
+# Indexes for table `user_reacts`
#
-CREATE INDEX {$db_prefix}user_likes_content ON {$db_prefix}user_likes (content_id, content_type);
-CREATE INDEX {$db_prefix}user_likes_liker ON {$db_prefix}user_likes (id_member);
+CREATE INDEX {$db_prefix}user_reacts_content ON {$db_prefix}user_reacts (content_id, content_type);
+CREATE INDEX {$db_prefix}user_reacts_reactor ON {$db_prefix}user_reacts (id_member);
#
# Table structure for `mentions`
@@ -2296,6 +2313,14 @@ VALUES (-1, 'search_posts'),
(2, 'access_mod_center');
# --------------------------------------------------------
+#
+# Dumping data for table `reactions`
+#
+
+INSERT INTO {$db_prefix}reactions
+ (id_reaction, name)
+VALUES (1, 'like');
+
#
# Dumping data for table `scheduled_tasks`
#
@@ -2642,7 +2667,7 @@ VALUES (0, 'alert_timeout', 10),
(0, 'member_report', 3),
(0, 'member_report_reply', 3),
(0, 'msg_auto_notify', 0),
- (0, 'msg_like', 1),
+ (0, 'msg_react', 1),
(0, 'msg_mention', 1),
(0, 'msg_notify_pref', 1),
(0, 'msg_notify_type', 1),
diff --git a/other/upgrade_2-1_MySQL.sql b/other/upgrade_2-1_MySQL.sql
index da91c8dd63..ee968fc8ea 100644
--- a/other/upgrade_2-1_MySQL.sql
+++ b/other/upgrade_2-1_MySQL.sql
@@ -1691,26 +1691,6 @@ VALUES
(-1, '1', 'drafts_show_saved_enabled', '1');
---#
-/******************************************************************************/
---- Adding support for likes
-/******************************************************************************/
----# Creating likes table.
-CREATE TABLE IF NOT EXISTS {$db_prefix}user_likes (
- id_member MEDIUMINT UNSIGNED DEFAULT '0',
- content_type CHAR(6) DEFAULT '',
- content_id INT UNSIGNED DEFAULT '0',
- like_time INT UNSIGNED NOT NULL DEFAULT '0',
- PRIMARY KEY (content_id, content_type, id_member),
- INDEX idx_content (content_id, content_type),
- INDEX idx_liker (id_member)
-) ENGINE=MyISAM;
----#
-
----# Adding likes column to the messages table. (May take a while)
-ALTER TABLE {$db_prefix}messages
-ADD COLUMN likes SMALLINT UNSIGNED NOT NULL DEFAULT '0';
----#
-
/******************************************************************************/
--- Adding support for mentions
/******************************************************************************/
@@ -3072,14 +3052,6 @@ UPDATE {$db_prefix}members
SET lngfile = REPLACE(lngfile, '-utf8', '');
---#
-/******************************************************************************/
---- Create index for messages likes
-/******************************************************************************/
----# Add Index for messages likes
-DROP INDEX idx_likes ON {$db_prefix}messages;
-CREATE INDEX idx_likes ON {$db_prefix}messages (likes);
----#
-
/******************************************************************************/
--- Aligning legacy column data
/******************************************************************************/
diff --git a/other/upgrade_2-1_PostgreSQL.sql b/other/upgrade_2-1_PostgreSQL.sql
index 96e2fedced..ac523ade75 100644
--- a/other/upgrade_2-1_PostgreSQL.sql
+++ b/other/upgrade_2-1_PostgreSQL.sql
@@ -1894,30 +1894,6 @@ INSERT INTO {$db_prefix}settings (variable, value) VALUES ('drafts_keep_days', '
INSERT INTO {$db_prefix}themes (id_member, id_theme, variable, value) VALUES (-1, '1', 'drafts_show_saved_enabled', '1') ON CONFLICT DO NOTHING;
---#
-/******************************************************************************/
---- Adding support for likes
-/******************************************************************************/
----# Creating likes table.
-CREATE TABLE IF NOT EXISTS {$db_prefix}user_likes (
- id_member int NOT NULL DEFAULT '0',
- content_type char(6) DEFAULT '',
- content_id int NOT NULL DEFAULT '0',
- like_time int NOT NULL DEFAULT '0',
- PRIMARY KEY (content_id, content_type, id_member)
-);
-
-DROP INDEX IF EXISTS {$db_prefix}user_likes_content;
-DROP INDEX IF EXISTS {$db_prefix}user_likes_liker;
-
-CREATE INDEX {$db_prefix}user_likes_content ON {$db_prefix}user_likes (content_id, content_type);
-CREATE INDEX {$db_prefix}user_likes_liker ON {$db_prefix}user_likes (id_member);
----#
-
----# Adding likes column to the messages table. (May take a while)
-ALTER TABLE {$db_prefix}messages
-ADD COLUMN IF NOT EXISTS likes smallint NOT NULL default '0';
----#
-
/******************************************************************************/
--- Adding support for mentions
/******************************************************************************/
diff --git a/other/upgrade_3-0_MySQL.sql b/other/upgrade_3-0_MySQL.sql
index 50cf5cd53e..ddf27bbf74 100644
--- a/other/upgrade_3-0_MySQL.sql
+++ b/other/upgrade_3-0_MySQL.sql
@@ -14,54 +14,54 @@ $members = [];
// Setup the case statement.
foreach (Lang::LANG_TO_LOCALE as $lang => $locale) {
- $statements[] = ' WHEN lngfile = {string:lang_' . $lang . '} THEN {string:locale_' . $locale . '}';
- $args['lang_' . $lang] = $lang;
- $args['locale_' . $locale] = $locale;
- $langs[] = $lang;
+ $statements[] = ' WHEN lngfile = {string:lang_' . $lang . '} THEN {string:locale_' . $locale . '}';
+ $args['lang_' . $lang] = $lang;
+ $args['locale_' . $locale] = $locale;
+ $langs[] = $lang;
}
$is_done = false;
while (!$is_done)
{
- nextSubStep($substep);
-
- // Skip errors here so we don't croak if the columns don't exist...
- $request = Db::$db->query('', '
- SELECT id_member
- FROM {db_prefix}members
- WHERE lngfile IN ({array_string:possible_languages})
- ORDER BY id_member
- LIMIT {int:limit}',
- [
- 'limit' => $limit,
- 'possible_languages' => $langs
- ]
- );
+ nextSubStep($substep);
+
+ // Skip errors here so we don't croak if the columns don't exist...
+ $request = Db::$db->query('', '
+ SELECT id_member
+ FROM {db_prefix}members
+ WHERE lngfile IN ({array_string:possible_languages})
+ ORDER BY id_member
+ LIMIT {int:limit}',
+ [
+ 'limit' => $limit,
+ 'possible_languages' => $langs
+ ]
+ );
if (Db::$db->num_rows($request) == 0) {
- $is_done = true;
- break;
- } else {
+ $is_done = true;
+ break;
+ } else {
while ($row = Db::$db->fetch_assoc($request)) {
- $members[] = $row['id_member'];
+ $members[] = $row['id_member'];
}
Db::$db->free_result($request);
- }
-
- // Nobody to convert, woohoo!
- if (empty($members)) {
- $is_done = true;
- break;
- } else {
- $args['search_members'] = $members;
- }
-
- Db::$db->query('', '
- UPDATE {db_prefix}members
- SET lngfile = CASE
- ' . implode(' ', $statements) . '
- ELSE {string:defaultLang} END
- WHERE id_member IN ({array_int:search_members})',
- $args
+ }
+
+ // Nobody to convert, woohoo!
+ if (empty($members)) {
+ $is_done = true;
+ break;
+ } else {
+ $args['search_members'] = $members;
+ }
+
+ Db::$db->query('', '
+ UPDATE {db_prefix}members
+ SET lngfile = CASE
+ ' . implode(' ', $statements) . '
+ ELSE {string:defaultLang} END
+ WHERE id_member IN ({array_int:search_members})',
+ $args
);
}
@@ -970,6 +970,81 @@ Db::$db->add_index(
INSERT IGNORE INTO {$db_prefix}settings (variable, value) VALUES ('spoofdetector_censor', '1');
---#
+/******************************************************************************/
+--- Adding support for reactions
+/******************************************************************************/
+---{
+// Make sure we haven't already done this...
+$cols = Db::$db->list_columns('messages');
+// If the reactions column exists in the messages table, there's nothing to do
+if(!in_array('reactions', $cols))
+{
+ // Does the user_likes table exist?
+ $table_exists = Db::$db->list_tables(false, '%user_likes');
+ if (!empty($table_exists))
+ {
+ // It already exists. Rename it.
+ upgrade_query("ALTER TABLE " . Db::$db->prefix . "user_likes RENAME TO " . Db::$db->prefix . "user_reacts");
+
+ // Add the new column
+ Db::$db->add_column('{db_prefix}user_reacts', ['name' => 'id_reaction', 'type' => 'smallint', 'not_null' => true, 'default' => '0']);
+
+ // Default react type is "like" for now...
+ upgrade_query("UPDATE " . Db::$db->prefix . "user_reacts SET id_reaction=1");
+
+ // Rename the like_time column
+ Db::$db->change_column('{db_prefix}user_reacts', 'like_time', ['name' => 'react_time']);
+
+ // Rename the index
+ upgrade_query("ALTER TABLE " . Db::$db->prefix . "user_reacts RENAME idx_liker TO idx_reactor");
+
+ // Rename the likes column in the messages table
+ upgrade_query("DROP INDEX idx_likes ON " . Db::$db-> prefix . "messages");
+ Db::$db->change_column('{db_prefix}messages', 'likes', ['name' => 'reactions']);
+ upgrade_query("ADD INDEX idx_reactions ON " . Db::$db->prefix . "messages (reactions)");
+
+ // Update user alert prefs
+ upgrade_query("UPDATE ". Db::$db->prefix . "user_alerts_prefs SET alert_pref='msg_react' WHERE alert_pref='msg_like'");
+
+ // Update permissions
+ upgrade_query("UPDATE ". Db::$db->prefix . "permissions SET permission='reactions_react' WHERE permission='likes_like'");
+
+ // And last but not least, the setting
+ upgrade_query("UPDATE " . Db::$db->prefix . "settings SET variable='enable_reacts' WHERE variable='enable_likes'");
+ }
+ else
+ {
+ // Add the table
+ upgrade_query("
+ CREATE TABLE " . Db::$db->prefix . "user_reacts (
+ id_member MEDIUMINT UNSIGNED DEFAULT '0',
+ id_reaction SMALLINT UNSIGNED DEFAULT '0',
+ content_type CHAR(6) DEFAULT '',
+ content_id INT UNSIGNED DEFAULT '0',
+ react_time INT UNSIGNED NOT NULL DEFAULT '0',
+ PRIMARY KEY (content_id, content_type, id_member),
+ INDEX idx_content (content_id, content_type),
+ INDEX idx_reactor (id_member)
+ ) ENGINE=InnoDB
+ ");
+
+ // Add the reactions column and related index to the messages table
+ Db::$db->add_column('{db_prefix}messages', ['name' => 'reactions', 'type' => 'smallint', 'not_null' => true, 'default' => '0']);
+ Db::$db->add_index('{db_prefix}messages', ['name' => 'idx_messages_reactions', 'columns' => ['reactions']]);
+ }
+
+ // Either way we want to add the new table
+ upgrade_query("
+ CREATE TABLE " . Db::$db->prefix . "reactions (
+ id_reaction SMALLINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
+ name varchar(255) NOT NULL DEFAULT ''
+ )
+ ");
+ // Default reaction is "like"
+ upgrade_query("INSERT INTO " . Db::$db->prefix . "reactions (id_reaction, name) VALUES (1, 'like')");
+}
+---}
+
/******************************************************************************/
--- Adding SMF version information to log_packages
/******************************************************************************/
diff --git a/other/upgrade_3-0_PostgreSQL.sql b/other/upgrade_3-0_PostgreSQL.sql
index 2aad556bed..fcbc2bed5d 100644
--- a/other/upgrade_3-0_PostgreSQL.sql
+++ b/other/upgrade_3-0_PostgreSQL.sql
@@ -13,54 +13,54 @@ $members = [];
// Setup the case statement.
foreach (Lang::LANG_TO_LOCALE as $lang => $locale) {
- $statements[] = ' WHEN lngfile = {string:lang_' . $lang . '} THEN {string:locale_' . $locale . '}';
- $args['lang_' . $lang] = $lang;
- $args['locale_' . $locale] = $locale;
- $langs[] = $lang;
+ $statements[] = ' WHEN lngfile = {string:lang_' . $lang . '} THEN {string:locale_' . $locale . '}';
+ $args['lang_' . $lang] = $lang;
+ $args['locale_' . $locale] = $locale;
+ $langs[] = $lang;
}
$is_done = false;
while (!$is_done)
{
- nextSubStep($substep);
-
- // Skip errors here so we don't croak if the columns don't exist...
- $request = Db::$db->query('', '
- SELECT id_member
- FROM {db_prefix}members
- WHERE lngfile IN ({array_string:possible_languages})
- ORDER BY id_member
- LIMIT {int:limit}',
- [
- 'limit' => $limit,
- 'possible_languages' => $langs
- ]
- );
+ nextSubStep($substep);
+
+ // Skip errors here so we don't croak if the columns don't exist...
+ $request = Db::$db->query('', '
+ SELECT id_member
+ FROM {db_prefix}members
+ WHERE lngfile IN ({array_string:possible_languages})
+ ORDER BY id_member
+ LIMIT {int:limit}',
+ [
+ 'limit' => $limit,
+ 'possible_languages' => $langs
+ ]
+ );
if (Db::$db->num_rows($request) == 0) {
- $is_done = true;
- break;
- } else {
+ $is_done = true;
+ break;
+ } else {
while ($row = Db::$db->fetch_assoc($request)) {
- $members[] = $row['id_member'];
+ $members[] = $row['id_member'];
}
Db::$db->free_result($request);
- }
-
- // Nobody to convert, woohoo!
- if (empty($members)) {
- $is_done = true;
- break;
- } else {
- $args['search_members'] = $members;
- }
-
- Db::$db->query('', '
- UPDATE {db_prefix}members
- SET lngfile = CASE
- ' . implode(' ', $statements) . '
- ELSE {string:defaultLang} END
- WHERE id_member IN ({array_int:search_members})',
- $args
+ }
+
+ // Nobody to convert, woohoo!
+ if (empty($members)) {
+ $is_done = true;
+ break;
+ } else {
+ $args['search_members'] = $members;
+ }
+
+ Db::$db->query('', '
+ UPDATE {db_prefix}members
+ SET lngfile = CASE
+ ' . implode(' ', $statements) . '
+ ELSE {string:defaultLang} END
+ WHERE id_member IN ({array_int:search_members})',
+ $args
);
}
@@ -852,6 +852,82 @@ CREATE INDEX {$db_prefix}idx_spoofdetector_name_id ON {$db_prefix}members (spoof
INSERT INTO {$db_prefix}settings (variable, value) VALUES ('spoofdetector_censor', '1') ON CONFLICT DO NOTHING;
---#
+/******************************************************************************/
+--- Adding support for reactions
+/******************************************************************************/
+---{
+// Make sure we haven't already done this...
+$cols = Db::$db->list_columns('messages');
+// If the reactions column exists in the messages table, there's nothing to do
+if(!in_array('reactions', $colsreb))
+{
+ // Does the user_likes table exist?
+ $table_exists = Db::$db->list_tables(false, '%user_likes');
+ if (!empty($table_exists))
+ {
+ // It already exists. Rename it.
+ upgrade_query("ALTER TABLE " . Db::$db->prefix . "user_likes RENAME TO " . Db::$db->prefix . "user_reacts");
+
+ // Add the new column
+ Db::$db->add_column('{db_prefix}user_reacts', ['name' => 'id_reaction', 'type' => 'smallint', 'null' => false, 'default' => '0']);
+
+ // Default react type is "like" for now...
+ upgrade_query("UPDATE " . Db::$db->prefix . "user_reacts SET id_reaction=1");
+
+ // Rename the like_time column
+ Db::$db->change_column('{db_prefix}user_reacts', 'like_time', ['name' => 'react_time']);
+
+ // Rename the index
+ upgrade_query("ALTER INDEX idx_liker RENAME TO idx_reactor");
+
+ // Rename the likes column in the messages table
+ upgrade_query("DROP INDEX idx_messages_likes");
+ Db::$db->change_column('{db_prefix}messages', 'likes', ['name' => 'reactions']);
+ upgrade_query("CREATE INDEX idx_messages_reactions ON " . Db::$db->prefix . "messages (reactions)");
+
+ // Update user alert prefs
+ upgrade_query("UPDATE " . Db::$db->prefix . "user_alerts_prefs SET alert_pref='msg_react' WHERE alert_pref='msg_like'");
+
+ // Update permissions
+ upgrade_query("UPDATE " . Db::$db->prefix . "permissions SET permission='reactions_react' WHERE permission='likes_like'");
+
+ // And last but not least, the setting
+ upgrade_query("UPDATE " . Db::$db->prefix . "settings SET variable='enable_reacts' WHERE variable='enable_likes'");
+ }
+ else
+ {
+ // Add the table
+ upgrade_query("
+ CREATE TABLE ". Db::$db->prefix . "user_reacts (
+ id_member mediumint default '0',
+ id_reaction smallint default '0',
+ content_type char(6) default '',
+ content_id int default '0',
+ react_time int unsigned not null default 0,
+ PRIMARY KEY (content_id, content_type, id_member),
+ INDEX idx_content (content_id, content_type),
+ INDEX idx_reactor (id_member)
+ )
+ ");
+
+ // Add the reactions column and related index to the messages table
+ Db::$db->add_column('{db_prefix}messages', ['name' => 'reactions', 'type' => 'smallint', 'not_null' => true, 'default' => '0']);
+ Db::$db->add_index('{db_prefix}messages', ['name' => 'idx_messages_reactions', 'columns' => ['reactions']]);
+ }
+
+ // Either way we want to add the new table
+ upgrade_query("CREATE SEQUENCE " . Db::$db->prefix ."reactions_seq");
+ upgrade_query("
+ CREATE TABLE " . Db::$db->prefix . "reactions (
+ id_reaction smallint default nextval('" . Db::$db->prefix . "reactions_seq'),
+ name varchar(255) not null default '',
+ )
+ ");
+ // Default reaction is "like"
+ upgrade_query("INSERT INTO " . Db::$db->prefix . "reactions (id_reaction, name) VALUES (1, 'like')");
+}
+---}
+
/******************************************************************************/
--- Adding SMF version information to log_packages
/******************************************************************************/