PluginProbe
wpForo Forum / 3.1.2
wpForo Forum v3.1.2
3.1.5 3.1.4 3.1.2 3.1.1 3.1.0 3.0.9 3.0.8 3.0.7 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.10 1.4.11 1.4.12 1.4.13 1.4.2 All 137 releases
wpforo / includes / installation.php

installation.php in wpForo Forum 3.1.2, at includes/installation.php

1,875 lines 74.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 // Exit if accessed directly
3 if( ! defined( 'ABSPATH' ) ) exit;
4
5 function wpforo_activation() {
6 #################################################################
7 // Create wpForo Tables /////////////////////////////////////////
8 wpforo_create_tables();
9
10 #################################################################
11 // Alter wpForo Tables //////////////////////////////////////////
12 wpforo_alter_tables();
13
14 #################################################################
15 // Permalink Settings ///////////////////////////////////////////
16 wpforo_fix_wp_permalink_structure();
17
18 #################################################################
19 // Access Sets //////////////////////////////////////////////////
20 wpforo_import_default_accesses();
21
22 #################################################################
23 // fix already installed options ////////////////////////////////
24 wpforo_fix_installed_options();
25
26 #################################################################
27 // synchronize users ////////////////////////////////////////////
28 WPF()->member->synchronize_users( 100 );
29 WPF()->member->init_current_user();
30 WPF()->member->clear_db_cache();
31
32 #################################################################
33 // Importing Language Packs and Phrases /////////////////////////
34 WPF()->phrase->xml_import( 'english.xml' );
35 WPF()->phrase->clear_cache();
36
37 #################################################################
38 // Creating Forum Page //////////////////////////////////////////
39 wpforo_create_forum_page();
40
41 #################################################################
42 // Forum Navigation and Menu ////////////////////////////////////
43 wpforo_import_default_menus();
44
45 #################################################################
46 // Boards ////////////////////////////////////////////////////
47 wpforo_import_default_board();
48
49 #################################################################
50 // Usergroup ////////////////////////////////////////////////////
51 wpforo_import_default_usergroups();
52
53 #################################################################
54 // Forums ////////////////////////////////////////////////////
55 wpforo_import_default_forums();
56
57 #################################################################
58 // UPDATE THEME OPTIONS ////////////////////////////////////////
59 wpforo_update_theme_options();
60
61 #################################################################
62 // wpForo Upgrade ///////////////////////////////
63 wpforo_upgrade();
64
65 #################################################################
66 // UPDATE VERSION - END /////////////////////////////////////////
67 wpforo_update_option( 'version', WPFORO_VERSION );
68 WPF()->notice->clear();
69
70 #################################################################
71 // DELETE ALL CAHCES /////////////////////////////////////////
72 wpforo_clean_cache();
73 // At the moment only Redis Object Cache should be flushed because it causes 404 error for all forum pages.
74 if( function_exists( 'redis_object_cache' ) ) {
75 wp_cache_flush();
76 }
77
78 #################################################################
79 // Reconcile AI crons against current connection state.
80 // Connected tenants get the recurring AI maintenance crons; everyone
81 // else (free installs that never enabled AI, disconnected accounts) gets
82 // stale AI events cleared out of wp_options.cron.
83 WPF()->ai_client->sync_cron_state();
84
85 // Schedule Email Queue Cleanup Cron (non-AI, always on)
86 WPF()->email_queue->schedule_cleanup_cron();
87 WPF()->email_queue->reset_stuck_processing();
88
89 // Note: AI Pending Topics Indexing Cron is scheduled when user
90 // enables auto-indexing in the admin settings (disabled by default)
91 }
92
93 function wpforo_upgrade() {
94 if( version_compare( wpforo_get_option( 'version', '', false ), '2.0.3', '<' ) ) {
95 // #### migrate old options to new settings
96 require_once WPFORO_DIR . "/includes/options-migration.php";
97 _wpforo_migrate_old_options_to_new();
98 _wpforo_migrate_old_widgets_to_new();
99 }
100
101 // Migrate menu for Recent Activity page (upgrade from 2.x to 3.x)
102 $old_version = wpforo_get_option( 'version', '', false );
103 if( $old_version && version_compare( $old_version, '3.0.0', '<' ) ) {
104 wpforo_migrate_recent_activity_menu();
105 wpforo_migrate_theme_styles();
106 }
107
108 // Migrate AI quality defaults to faster tiers (3.0.3+)
109 // Runs for all versions - has its own one-time flag check
110 wpforo_migrate_ai_quality_defaults();
111 }
112
113 function wpforo_create_tables() {
114 require_once( WPFORO_DIR . '/includes/install-sql.php' );
115 foreach( wpforo_get_install_sqls() as $sql ) {
116 if( false === @WPF()->db->query( $sql ) ) {
117 @WPF()->db->query( preg_replace( '#\)\s*ENGINE.*$#isu', ')', $sql ) );
118 }
119 }
120 }
121
122 function wpforo_alter_tables() {
123 #################################################################
124 // ADD `status` field in `languages` TABLE //////////////////////////////////////////////
125 if( ! wpforo_db_check( [ 'table' => WPF()->tables->languages, 'col' => 'status', 'check' => 'col_exists' ] ) ) {
126 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->languages . "` ADD COLUMN `status` TINYINT(1) NOT NULL DEFAULT 1" );
127 }
128
129 #################################################################
130 // ADD `package` field in `phrases` TABLE //////////////////////////////////////////////
131 if( ! wpforo_db_check( [ 'table' => WPF()->tables->phrases, 'col' => 'package', 'check' => 'col_exists' ] ) ) {
132 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->phrases . "` ADD `package` VARCHAR(255) NOT NULL DEFAULT 'wpforo'" );
133 }
134 // WPF()->phrase->clear_cache();
135
136 #################################################################
137 // ADD `private` field in TOPIC TABLE ///////////////////////////
138 if( ! wpforo_db_check( [ 'table' => WPF()->tables->topics, 'col' => 'private', 'check' => 'col_exists' ] ) ) {
139 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` ADD `private` TINYINT(1) NOT NULL DEFAULT '0', ADD INDEX `is_private` (`private`);" );
140 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` ADD INDEX `own_private` ( `userid`, `private`);" );
141 }
142 // ADD `solved` field in TOPIC TABLE ///////////////////////////
143 if( ! wpforo_db_check( [ 'table' => WPF()->tables->topics, 'col' => 'solved', 'check' => 'col_exists' ] ) ) {
144 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` ADD `solved` TINYINT(1) NOT NULL DEFAULT 0 AFTER `type`, ADD INDEX `solved` (`solved`)" );
145 @WPF()->db->query(
146 "UPDATE `" . WPF()->tables->topics . "` t
147 INNER JOIN `" . WPF()->tables->posts . "` p ON p.`topicid` = t.`topicid` AND p.`is_answer` = 1
148 SET t.`solved` = 1
149 WHERE t.`solved` = 0"
150 );
151 wpforo_clean_cache();
152 }
153 // ADD `status` field in TOPICS & POSTS TABLE ///////////////////////////
154 if( ! wpforo_db_check( [ 'table' => WPF()->tables->topics, 'col' => 'status', 'check' => 'col_exists' ] ) ) {
155 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` ADD `status` TINYINT(1) NOT NULL DEFAULT '0', ADD INDEX `status` (`status`);" );
156 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD `status` TINYINT(1) NOT NULL DEFAULT '0', ADD INDEX `status` (`status`);" );
157 }
158 // ADD `name` and `email` field in TOPIC TABLE ///////////////////////////
159 if( ! wpforo_db_check( [ 'table' => WPF()->tables->topics, 'col' => 'name', 'check' => 'col_exists' ] ) ) {
160 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` ADD `name` VARCHAR(50) NOT NULL, ADD `email` VARCHAR(50) NOT NULL" );
161 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD `name` VARCHAR(50) NOT NULL, ADD `email` VARCHAR(50) NOT NULL" );
162 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` ADD KEY `email` (`email`)" );
163 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD KEY `email` (`email`)" );
164 }
165 // ADD `utitle`, `role` and `access` to USERGROUP TABLE /////////
166 if( ! wpforo_db_check( [ 'table' => WPF()->tables->usergroups, 'col' => 'utitle', 'check' => 'col_exists' ] ) ) {
167 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->usergroups . "` ADD `utitle` VARCHAR(100), ADD `role` VARCHAR(50), ADD `access` VARCHAR(50)" );
168 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `utitle` = 'Admin', `role` = 'administrator', `access` = 'full' WHERE `groupid` = 1" );
169 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `utitle` = 'Moderator', `role` = 'editor', `access` = 'moderator' WHERE `groupid` = 2" );
170 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `utitle` = 'Registered', `role` = 'subscriber', `access` = 'standard' WHERE `groupid` = 3" );
171 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `utitle` = 'Guest', `role` = '', `access` = 'read_only' WHERE `groupid` = 4" );
172 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `utitle` = 'Customer', `role` = 'customer', `access` = 'standard' WHERE `groupid` = 5" );
173 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `utitle` = 'name', `role` = 'subscriber', `access` = 'standard' WHERE `utitle` IS NULL OR `utitle` = ''" );
174 }
175 #################################################################
176 // ADD `color` field in usergroups TABLE ///////////////////////////
177 if( ! wpforo_db_check( [ 'table' => WPF()->tables->usergroups, 'col' => 'color', 'check' => 'col_exists' ] ) ) {
178 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->usergroups . "` ADD `color` varchar(7) NOT NULL DEFAULT ''" );
179 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `color` = '#FF3333' WHERE `groupid` = 1" );
180 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `color` = '#0066FF' WHERE `groupid` = 2" );
181 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `color` = '#222222' WHERE `groupid` = 4" );
182 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `color` = '#993366' WHERE `groupid` = 5" );
183 }
184 #################################################################
185 // ADD `visible` field in usergroups TABLE ///////////////////////////
186 if( ! wpforo_db_check( [ 'table' => WPF()->tables->usergroups, 'col' => 'visible', 'check' => 'col_exists' ] ) ) {
187 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->usergroups . "` ADD `visible` TINYINT(1) NOT NULL DEFAULT 1;" );
188 }
189 #################################################################
190 // ADD `status` field in `languages` TABLE //////////////////////////////////////////////
191 if( ! wpforo_db_check( [ 'table' => WPF()->tables->usergroups, 'col' => 'is_default', 'check' => 'col_exists' ] ) ) {
192 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->usergroups . "` ADD COLUMN `is_default` TINYINT(1) NOT NULL DEFAULT 0" );
193 }
194 #################################################################
195 // ADD `online_time` field in profiles TABLE ///////////////////////////
196 if( ! wpforo_db_check( [ 'table' => WPF()->tables->profiles, 'col' => 'online_time', 'check' => 'col_exists' ] ) ) {
197 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->profiles . "` ADD `online_time` INT UNSIGNED NOT NULL DEFAULT 0, ADD KEY (`online_time`)" );
198 }
199 // ADD `is_email_confirmed` field in profiles TABLE ///////////////////////////
200 if( ! wpforo_db_check( [ 'table' => WPF()->tables->profiles, 'col' => 'is_email_confirmed', 'check' => 'col_exists' ] ) ) {
201 WPF()->db->query( "ALTER TABLE `" . WPF()->tables->profiles . "` ADD `is_email_confirmed` TINYINT(1) NOT NULL DEFAULT 0, ADD KEY (`is_email_confirmed`)" );
202 WPF()->db->query(
203 "UPDATE `" . WPF()->tables->profiles . "`
204 JOIN `" . WPF()->tables->subscribes . "`
205 ON `" . WPF()->tables->subscribes . "`.`userid` = `" . WPF()->tables->profiles . "`.`userid`
206 SET `" . WPF()->tables->profiles . "`.`is_email_confirmed` = 1
207 WHERE `" . WPF()->tables->subscribes . "`.`active` = 1"
208 );
209 WPF()->db->query( "UPDATE `" . WPF()->tables->profiles . "` SET `is_email_confirmed` = 1 WHERE `groupid` = 1" );
210 }
211 #################################################################
212 // DROP uname unique key from profiles TABLE ///////////////////////////
213 if( wpforo_db_check( [ 'table' => WPF()->tables->profiles, 'col' => 'UNIQUE USERNAME', 'check' => 'key_exists' ] ) ) {
214 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->profiles . "` DROP KEY `UNIQUE USERNAME`" );
215 }
216 if( wpforo_db_check( [ 'table' => WPF()->tables->profiles, 'col' => 'UNIQUE ID', 'check' => 'key_exists' ] ) ) {
217 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->profiles . "` DROP KEY `UNIQUE ID`" );
218 }
219 #################################################################
220 // ADD `private` field in post TABLE ///////////////////////////
221 if( ! wpforo_db_check( [ 'table' => WPF()->tables->posts, 'col' => 'private', 'check' => 'col_exists' ] ) ) {
222 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD `private` TINYINT(1) NOT NULL DEFAULT 0, ADD INDEX `is_private` (`private`)" );
223 }
224 #################################################################
225 //Add user_name col in subsciption table///////////////////////////
226 if( ! wpforo_db_check( [ 'table' => WPF()->tables->subscribes, 'col' => 'user_name', 'check' => 'col_exists' ] ) ) {
227 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->subscribes . "` ADD `user_name` VARCHAR(60) NOT NULL DEFAULT ''" );
228 }
229 //Add user_email col in subsciption table
230 if( ! wpforo_db_check( [ 'table' => WPF()->tables->subscribes, 'col' => 'user_email', 'check' => 'col_exists' ] ) ) {
231 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->subscribes . "` ADD `user_email` VARCHAR(60) NOT NULL DEFAULT ''" );
232 }
233 //Add indexes for subscribe new fields
234 if( ! wpforo_db_check( [ 'table' => WPF()->tables->subscribes, 'col' => 'fld_group_unq', 'check' => 'key_exists' ] ) ) {
235 $args = [ 'table' => WPF()->tables->subscribes, 'col' => 'itemid', 'check' => 'key_exists' ];
236 if( wpforo_db_check( $args ) ) @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->subscribes . "` DROP KEY `itemid`" );
237 wpforo_add_unique_key( WPF()->tables->subscribes, 'subid', 'fld_group_unq', '`itemid`, `type`, `userid`, `user_email`(60)' );
238 }
239 if( strtolower( (string) wpforo_db_check( [ 'table' => WPF()->tables->subscribes, 'col' => 'type', 'check' => 'col_type' ] ) ) !== 'varchar(50)' ) {
240 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->subscribes . "` MODIFY `type` VARCHAR(50) NOT NULL" );
241 }
242 ####################################################################
243 //Add index for double condition queries to avoid SQl caching///////
244 if( ! wpforo_db_check( [ 'table' => WPF()->tables->posts, 'col' => 'forumid_status', 'check' => 'key_exists' ] ) ) {
245 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD KEY `forumid_status` (`forumid`,`status`)" );
246 }
247 if( ! wpforo_db_check( [ 'table' => WPF()->tables->posts, 'col' => 'topicid_status', 'check' => 'key_exists' ] ) ) {
248 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD KEY `topicid_status` (`topicid`,`status`)" );
249 }
250 if( ! wpforo_db_check( [ 'table' => WPF()->tables->posts, 'col' => 'topicid_solved', 'check' => 'key_exists' ] ) ) {
251 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD KEY `topicid_solved` (`topicid`,`is_answer`)" );
252 }
253 if( ! wpforo_db_check( [ 'table' => WPF()->tables->topics, 'col' => 'forumid_status', 'check' => 'key_exists' ] ) ) {
254 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` ADD KEY `forumid_status` (`forumid`,`status`)" );
255 }
256 $topic_slug_key = @WPF()->db->get_row( "SHOW KEYS FROM `" . WPF()->tables->topics . "` WHERE `Key_name` LIKE 'slug'", ARRAY_A );
257 if( $topic_slug_key && intval( wpfval( $topic_slug_key, 'Non_unique' ) ) === 0 ) {
258 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` DROP INDEX slug, ADD KEY slug (`slug`(191))" );
259 }
260 if( ! wpforo_db_check( [ 'table' => WPF()->tables->posts, 'col' => 'topicid_parentid', 'check' => 'key_exists' ] ) ) {
261 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD KEY `topicid_parentid` (`topicid`,`parentid`)" );
262 }
263 #################################################################
264 // ADD `secondary_groupids` field in profiles TABLE //////////////
265 /*if( ! wpforo_db_check( [ 'table' => WPF()->tables->profiles, 'col' => 'secondary_groups', 'check' => 'col_exists' ] ) ) {
266 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->profiles . "` ADD `secondary_groups` VARCHAR(255)" );
267 }*/
268 if( ! wpforo_db_check( [ 'table' => WPF()->tables->usergroups, 'col' => 'secondary', 'check' => 'col_exists' ] ) ) {
269 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->usergroups . "` ADD `secondary` TINYINT(1) NOT NULL DEFAULT 0;" );
270 @WPF()->db->query( "UPDATE `" . WPF()->tables->usergroups . "` SET `secondary` = 1 WHERE `groupid` IN(3,5)" );
271 }
272 #################################################################
273 // ADD `fields` field in profiles TABLE ////////////////////////
274 if( ! wpforo_db_check( [ 'table' => WPF()->tables->profiles, 'col' => 'fields', 'check' => 'col_exists' ] ) ) {
275 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->profiles . "` ADD `fields` LONGTEXT" );
276 }
277 #################################################################
278 // Change `phrase_key` type in phrases TABLE ///////////////////
279 if( strtolower( (string) wpforo_db_check( [ 'table' => WPF()->tables->phrases, 'col' => 'phrase_key', 'check' => 'col_type' ] ) ) !== 'text' ) {
280 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->phrases . "` MODIFY `phrase_key` TEXT" );
281 }
282 #################################################################
283 // ADD `prefix` and `tags` fields in TOPIC TABLE ///////////////
284 if( ! wpforo_db_check( [ 'table' => WPF()->tables->topics, 'col' => 'tags', 'check' => 'col_exists' ] ) ) {
285 @WPF()->db->query(
286 "ALTER TABLE `" . WPF()->tables->topics . "`
287 ADD `prefix` VARCHAR(100) NOT NULL DEFAULT '',
288 ADD `tags` TEXT,
289 ADD KEY (`prefix`),
290 ADD KEY (`tags`(190))"
291 );
292 }
293 #################################################################
294 //Add last_post indexes for forums and topics tables ////////////
295 if( ! wpforo_db_check( [ 'table' => WPF()->tables->forums, 'col' => 'last_postid', 'check' => 'key_exists' ] ) ) {
296 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->forums . "` ADD KEY(`last_postid`)" );
297 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "` ADD KEY `forumid_status_private` ( `forumid`,`status`,`private` ), ADD KEY(`last_post`)" );
298 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD KEY `forumid_status_private` (`forumid`, `status`, `private`)" );
299 }
300 #################################################################
301 //Add cover and cover_height fields to forums table ////////////
302 if( ! wpforo_db_check( [ 'table' => WPF()->tables->forums, 'col' => 'cover', 'check' => 'col_exists' ] ) ) {
303 @WPF()->db->query(
304 "ALTER TABLE `" . WPF()->tables->forums . "`
305 ADD COLUMN `cover` BIGINT UNSIGNED NOT NULL DEFAULT 0,
306 ADD COLUMN `cover_height` INT(4) UNSIGNED NOT NULL DEFAULT 150"
307 );
308 }
309
310 #################################################################
311 //rename column from cat_layout to layout in forums table ////////////
312 if( wpforo_db_check( [ 'table' => WPF()->tables->forums, 'col' => 'cat_layout', 'check' => 'col_exists' ] ) ) {
313 @WPF()->db->query(
314 "ALTER TABLE `" . WPF()->tables->forums . "`
315 CHANGE COLUMN `cat_layout` `layout` tinyint(1) UNSIGNED NOT NULL DEFAULT 0"
316 );
317 }
318 #################################################################
319 //Add unique keys in VISITS TABLE ///////////////////////////////
320 if( ! wpforo_db_check( [ 'table' => WPF()->tables->visits, 'col' => 'unique_tracking', 'check' => 'key_exists' ] ) ) {
321 wpforo_add_unique_key( WPF()->tables->visits, 'id', 'unique_tracking', '`userid`,`ip`,`forumid`,`topicid`' );
322 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->visits . "` ADD KEY `time_forumid` (`time`, `forumid`)" );
323 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->visits . "` ADD KEY `time_topicid` (`time`, `topicid`)" );
324 }
325 #################################################################
326 // ADD `color` field in forums TABLE ///////////////////////////
327 if( ! wpforo_db_check( [ 'table' => WPF()->tables->forums, 'col' => 'color', 'check' => 'col_exists' ] ) ) {
328 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->forums . "` ADD `color` varchar(7) NOT NULL DEFAULT ''" );
329 @WPF()->db->query( "UPDATE `" . WPF()->tables->forums . "` SET `color` = concat('#',SUBSTRING((lpad(hex(round(rand() * 10000000)),6,0)),-6))" );
330 }
331 #################################################################
332 // ADD `root` fields in POSTS TABLE ///////////////
333 //$post_table_count = @WPF()->db->get_var("SELECT COUNT(*) FROM `".WPF()->tables->posts."`");
334 //if( $post_table_count < 100000 ){
335 if( ! wpforo_db_check( [ 'table' => WPF()->tables->posts, 'col' => 'root', 'check' => 'col_exists' ] ) ) {
336 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->posts . "` ADD `root` BIGINT, ADD KEY(`root`)" );
337 }
338 //}
339 #################################################################
340 // ADD `new` in ACTIVITY TABLE /////////////////////////////////
341 if( ! wpforo_db_check( [ 'table' => WPF()->tables->activity, 'col' => 'new', 'check' => 'col_exists' ] ) ) {
342 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->activity . "` ADD `new` TINYINT NOT NULL DEFAULT '0' AFTER `permalink`, ADD KEY `itemtype_userid_new` (`itemtype`, `userid`, `new`)" );
343 }
344
345 #################################################################
346 // Change `last_userid` type in forums TABLE ///////////////////
347 if( strtolower( (string) wpforo_db_check( [ 'table' => WPF()->tables->forums, 'col' => 'last_userid', 'check' => 'col_type' ] ) ) !== 'varchar(255)' ) {
348 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->forums . "` CHANGE `last_userid` `last_userid` VARCHAR(255) NOT NULL" );
349 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->forums . "` ADD KEY `last_userid` (`last_userid`(191));" );
350 }
351
352 ################################################################
353 // likes and votes Table to new reactions table ////////////////
354 if( wpforo_db_check( [ 'table' => wpforo_fix_table_name( 'votes' ), 'check' => 'table_exists' ] ) ) {
355 $sql = "INSERT IGNORE INTO `" . WPF()->tables->reactions . "` (
356 SELECT NULL, `userid`, `postid`, `post_userid`, `reaction`, IF( `reaction` = 1, 'up', 'down' ), NULL, NULL
357 FROM `" . wpforo_fix_table_name( 'votes' ) . "`
358 )";
359 WPF()->db->query( $sql );
360 $sql = "DROP TABLE `" . wpforo_fix_table_name( 'votes' ) . "`";
361 WPF()->db->query( $sql );
362 }
363
364 if( wpforo_db_check( [ 'table' => wpforo_fix_table_name( 'likes' ), 'check' => 'table_exists' ] ) ) {
365 $sql = "INSERT IGNORE INTO `" . WPF()->tables->reactions . "` (
366 SELECT NULL, `userid`, `postid`, `post_userid`, 1, 'up', NULL, NULL
367 FROM `" . wpforo_fix_table_name( 'likes' ) . "`
368 )";
369 WPF()->db->query( $sql );
370 $sql = "DROP TABLE `" . wpforo_fix_table_name( 'likes' ) . "`";
371 WPF()->db->query( $sql );
372 }
373
374 if( ! wpforo_db_check( [ 'table' => WPF()->tables->reactions, 'col' => 'performance', 'check' => 'key_exists' ] ) ) {
375 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->reactions . "` ADD KEY `performance` (`postid`,`post_userid`,`type`)" );
376 }
377
378 #################################################################
379 // ADD `type` field in forums TABLE for ticket/private forums
380 // Default is 'forum', 'ticket_forum' forces all topics to be private
381 if( ! wpforo_db_check( [ 'table' => WPF()->tables->forums, 'col' => 'type', 'check' => 'col_exists' ] ) ) {
382 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->forums . "` ADD `type` VARCHAR(20) NOT NULL DEFAULT 'forum'" );
383 }
384
385 // ADD indexed, local, cloud, task_tag fields and indexes in TOPICS TABLE ///////////////////////////
386 if( ! wpforo_db_check( [ 'table' => WPF()->tables->topics, 'col' => 'indexed', 'check' => 'col_exists' ] ) ) {
387 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->topics . "`
388 ADD `indexed` VARCHAR(32),
389 ADD `local` TINYINT(1) NOT NULL DEFAULT 0,
390 ADD `cloud` TINYINT(1) NOT NULL DEFAULT 0,
391 ADD `task_tag` TINYINT(1) NOT NULL DEFAULT 0,
392 ADD INDEX `indexed` (`indexed`),
393 ADD INDEX `local` (`local`),
394 ADD INDEX `cloud` (`cloud`),
395 ADD INDEX `task_tag` (`task_tag`)" );
396 }
397
398 ################################################################
399 // profiles table fields fixing ////////////////////////////////
400 if( wpforo_db_check( [ 'table' => WPF()->tables->profiles, 'col' => 'facebook', 'check' => 'col_exists' ] ) ) {
401 $sql = "SELECT `userid`, `facebook`, `twitter`, `skype`, `fields`
402 FROM `" . WPF()->tables->profiles . "`
403 WHERE (`facebook` IS NOT NULL AND `facebook` <> '')
404 OR (`twitter` IS NOT NULL AND `twitter` <> '')
405 OR (`skype` IS NOT NULL AND `skype` <> '')";
406 if( $users = (array) WPF()->db->get_results( $sql, ARRAY_A ) ) {
407 foreach( $users as $user ) {
408 $user['userid'] = wpforo_bigintval( $user['userid'] );
409 $facebook = trim( (string) $user['facebook'] );
410 $twitter = trim( (string) $user['twitter'] );
411 $skype = trim( (string) $user['skype'] );
412 $user['fields'] = array_merge( (array) json_decode( $user['fields'], true ), compact( 'facebook', 'twitter', 'skype' ) );
413 $user['fields'] = wp_json_encode( $user['fields'] );
414 WPF()->db->update(
415 WPF()->tables->profiles,
416 [ 'fields' => $user['fields'] ],
417 [ 'userid' => $user['userid'] ],
418 '%s',
419 '%d'
420 );
421 }
422 }
423
424 @WPF()->db->query(
425 "ALTER TABLE `" . WPF()->tables->profiles . "`
426 DROP COLUMN `username`,
427 DROP COLUMN `last_login`,
428 DROP COLUMN `icq`,
429 DROP COLUMN `aim`,
430 DROP COLUMN `yahoo`,
431 DROP COLUMN `msn`,
432 DROP COLUMN `gtalk`,
433 DROP COLUMN `like`,
434 DROP COLUMN `site`,
435 DROP COLUMN `facebook`,
436 DROP COLUMN `twitter`,
437 DROP COLUMN `skype`,
438 ADD COLUMN `topics` INT UNSIGNED NOT NULL DEFAULT 0 AFTER `posts`,
439 ADD COLUMN `reactions_in` TEXT AFTER `comments`,
440 ADD COLUMN `reactions_out` TEXT AFTER `reactions_in`,
441 ADD COLUMN `points` INT NOT NULL DEFAULT 0 AFTER `reactions_out`,
442 CHANGE COLUMN `secondary_groups` `secondary_groupids` VARCHAR(255) AFTER `groupid`,
443 CHANGE COLUMN `rank` `custom_points` INT NOT NULL DEFAULT 0 AFTER `points`,
444 MODIFY COLUMN `avatar` VARCHAR(255) AFTER `secondary_groupids`,
445 MODIFY COLUMN `online_time` INT UNSIGNED AFTER `custom_points`,
446 MODIFY COLUMN `timezone` VARCHAR(255) NOT NULL DEFAULT '' AFTER `online_time`,
447 MODIFY COLUMN `location` VARCHAR(255) AFTER `timezone`,
448 ADD COLUMN `cover` VARCHAR(255) NOT NULL DEFAULT '' AFTER `avatar`,
449 ADD COLUMN `is_mention_muted` TINYINT(1) NOT NULL DEFAULT 0 AFTER `is_email_confirmed`"
450 );
451 }
452
453 #################################################################
454 // Add content_type column to ai_embeddings table //////////////
455 if( ! wpforo_db_check( [ 'table' => WPF()->tables->ai_embeddings, 'col' => 'content_type', 'check' => 'col_exists' ] ) ) {
456 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->ai_embeddings . "` ADD COLUMN `content_type` VARCHAR(20) NOT NULL DEFAULT 'forum' COMMENT 'forum or WordPress post_type'" );
457 @WPF()->db->query( "ALTER TABLE `" . WPF()->tables->ai_embeddings . "` ADD KEY `idx_content_type` (`content_type`)" );
458 }
459
460 WPF()->db->query( "TRUNCATE TABLE `" . WPF()->tables->visits . "`" );
461 }
462
463 function wpforo_board_repair( $boardid ) {
464 if( ! wpforo_is_admin() ) return;
465 if( ! current_user_can( 'activate_plugins' ) ) return;
466
467 WPF()->change_board( $boardid );
468
469 wpforo_activation();
470
471 foreach( wpforo_get_addons_info() as $key => $addon ) {
472 if( ( $addon['base'] || wpforo_is_module_enabled( $key ) ) && is_callable( wpfval( $addon, 'install_func' ) ) ) {
473 call_user_func( $addon['install_func'] );
474 }
475 }
476 }
477
478 function wpforo_board_uninstall( $boardid ) {
479 if( ! wpforo_is_admin() ) return;
480 if( ! current_user_can( 'activate_plugins' ) ) return;
481
482 WPF()->change_board( $boardid );
483 WPF()->board->delete( $boardid );
484 foreach( WPF()->_tables as $table ) WPF()->db->query( "DROP TABLE IF EXISTS `" . wpforo_fix_table_name( $table ) . "`" );
485
486 WPF()->db->query(
487 "DELETE FROM `" . WPF()->db->options . "`
488 WHERE `option_name` LIKE 'widget_wpforo_widget_%'
489 OR `option_name` REGEXP '^" . wpforo_prefix() . "'"
490 );
491 WPF()->db->query( "DELETE FROM `" . WPF()->db->usermeta . "` WHERE `meta_key` REGEXP '^" . wpforo_prefix() . "'" );
492
493 wpforo_remove_directory( WPF()->folders['upload']['dir'] );
494 }
495
496 function wpforo_uninstall() {
497 if( ! wpforo_is_admin() ) return;
498 if( ! current_user_can( 'activate_plugins' ) ) return;
499 require_once ABSPATH . 'wp-admin/includes/plugin.php';
500 foreach( WPF()->board->get_boards( [ 'orderby' => '`boardid` DESC' ] ) as $board ) wpforo_board_uninstall( $board['boardid'] );
501 WPF()->db->query(
502 "DELETE FROM `" . WPF()->db->options . "`
503 WHERE `option_name` LIKE 'widget_wpforo%'
504 OR `option_name` REGEXP '^" . WPF()->base_prefix . "'"
505 );
506 WPF()->db->query( "DELETE FROM `" . WPF()->db->usermeta . "` WHERE `meta_key` REGEXP '^" . WPF()->base_prefix . "'" );
507 foreach( WPF()->_base_tables as $table ) WPF()->db->query( "DROP TABLE IF EXISTS `" . wpforo_fix_table_name( $table ) . "`" );
508 deactivate_plugins( WPFORO_BASENAME );
509 }
510
511 function wpforo_profile_notice() {
512 if( is_multisite() ) {
513 $users = WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->db->usermeta . "` WHERE `meta_key` LIKE '" . WPF()->blog_prefix . "capabilities'" );
514 } else {
515 $users = WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->db->users . "`" );
516 }
517 $profiles = WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->tables->profiles . "`" );
518 $delta = $users - $profiles;
519 $status = ( $delta > 2 ) ? round( ( ( $profiles * 100 ) / $users ), 1 ) . '% (' . $profiles . ' / ' . $users . ') ' : '100%';
520 if( $status === '100%' ) return null;
521 $btext = ( $profiles == 0 ) ? __( 'Start Profile Synchronization', 'wpforo' ) : __( 'Continue Synchronization', 'wpforo' );
522 $url = wp_nonce_url( admin_url( 'admin.php?page=wpforo-overview&wpfaction=synch_user_profiles' ), 'wpforo_synch_user_profiles' );
523 $class = 'wpforo-mnote notice notice-warning is-dismissible';
524 $note = __( 'This process may take a few seconds or dozens of minutes, please be patient and don\'t close this page.', 'wpforo' );
525 $info = __( 'You can permanently disable this message using this documentation', 'wpforo' );
526 $button = '<a href="' . $url . '" class="button button-primary button-large" style="font-size:14px;">' . $btext . ' &gt;&gt;</a>';
527 $header = __( 'wpForo Forum Installation | ', 'wpforo' );
528 $message = __( 'Forum users\' profile data are not synchronized yet, this step is required! Please click the button below to complete installation.', 'wpforo' );
529 echo '<div class="' . $class . '" style="padding:15px 20px;"><h2 style="margin:0;">' . esc_html(
530 $header
531 ) . $status . ' </h2><p style="font-size:15px;margin:5px 0;">' . $message . '</p><p style="margin:0 0 10px 0;">' . $button . '</p><hr /><p style="margin:0;color:#dd0000;">' . $note . '</p></div>';
532 }
533
534 function wpforo_database_notice() {
535 $url = admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'tools' ) . '&tab=tables' );
536 $class = 'wpforo-dbnote notice notice-error is-dismissible';
537 $button = '<a href="' . $url . '" class="button button-primary button-large" style="font-size:13px;">Go to Database Troubleshooter &gt;&gt;</a>';
538 $header = __( 'wpForo Database Update Problem - Action Required!', 'wpforo' );
539 $message = __( 'Forum database is not updated properly. Please click the button below for further instruction.', 'wpforo' );
540 echo '<div class="' . $class . '" style="padding:15px 20px;"><h2 style="margin:0;">' . esc_html(
541 $header
542 ) . ' </h2><p style="font-size:15px;margin:5px 0;">' . $message . '</p><p style="margin:15px 0 0 0;">' . $button . '</p></div>';
543 }
544
545 function wpforo_cache_information() {
546 $plugin_names = [];
547 $plugin_steps = [];
548 $not_excluded_plugins = WPF()->cache->cache_plugins_status();
549 if( ! empty( $not_excluded_plugins ) ) {
550 foreach( $not_excluded_plugins as $key => $plugin ) {
551 $plugin_names[ $key ] = '&laquo;' . $plugin['name'] . '&raquo;';
552 $plugin_steps[ $key ] = '<h4 style="margin: 0; font-size: 14px;">' . __( 'Exclude forum page(s) from' ) . ' ' . $plugin['name'] . ' ' . __(
553 'plugin',
554 'wpforo'
555 ) . '</h4><ol style="font-size: 14px; color: #6203a2;"><li style="margin-bottom: 4px;">' . implode( '</li><li style="margin-bottom: 4px;">', $plugin['steps'] ) . '</ol></li>';
556 }
557 }
558 $class = 'wpforo-cache-conflict-note notice notice-error is-dismissible';
559 $note = sprintf(
560 __( 'If you have already excluded the forum page from your cache plugin please ignore and close this message using the top %s (x) button.', 'wpforo' ),
561 ( is_rtl() ? __( 'left', 'wpforo' ) : __( 'right', 'wpforo' ) )
562 );
563 $info = __( 'Please find more information here: ', 'wpforo' ) . '<a href="https://wpforo.com/community/faq/wpforo-and-cache-plugins/" target="_blank">wpForo and Cache Plugins</a>';
564 $header = 'wpForo ' . __( ' and ', 'wpforo' ) . implode( __( ' and ', 'wpforo' ), $plugin_names ) . ' ' . __( 'conflict', 'wpforo' ) . ' - ' . __( 'Action Required!', 'wpforo' );
565 $message = __(
566 'Please exclude the forum page from your cache plugin! wpForo has a built-in cache system. It does dynamic cache of all forum pages, which will be affected by your cache plugin and the forum data will not be updated on the front-end. The user login and logout actions will also be corrupted.',
567 'wpforo'
568 ) . '<br />' . implode( '<br>', $plugin_steps );
569
570 echo '<div class="' . $class . '" style="padding:15px 20px;"><h2 style="margin:0;">' . esc_html(
571 $header
572 ) . ' </h2><p style="font-size:15px;margin:10px 0;">' . $message . '</p><hr /><p style="margin:0;color:#dd0000; font-size:14px;">' . $note . '</p><p style="margin:0;font-size:12px;">' . $info . '</p></div>';
573 echo "<script>jQuery(document).on('click', '.wpforo-cache-conflict-note .notice-dismiss', function () {jQuery.ajax({ url: ajaxurl, data: { action: 'dismiss_wpforo_cache_conflict_note' } })})</script>";
574 }
575
576 function wpforo_get_shortcode_pageid( $exclude = [] ) {
577 $exclude = array_filter( array_map( 'wpforo_bigintval', (array) $exclude ) );
578 $sql = "SELECT `ID` FROM `" . WPF()->db->posts . "`
579 WHERE `post_content` LIKE '%[wpforo]%'
580 AND `post_status` LIKE 'publish'
581 AND `post_type` IN('" . implode( "','", wpforo_get_blog_content_types() ) . "')";
582 if( $exclude ) $sql .= " AND `ID` NOT IN(" . implode( ',', $exclude ) . ")";
583
584 return WPF()->db->get_var( $sql );
585 }
586
587 function wpforo_create_forum_page() {
588 $pageid = WPF()->board->get_current( 'pageid' );
589 if( ! $pageid || ! WPF()->db->get_var(
590 "SELECT `ID` FROM `" . WPF(
591 )->db->posts . "` WHERE `ID` = '" . $pageid . "' AND ( `post_content` LIKE '%[wpforo]%' OR `post_content` LIKE '%[wpforo-index]%' ) AND `post_status` LIKE 'publish' AND `post_type` IN('" . implode(
592 "','",
593 wpforo_get_blog_content_types()
594 ) . "')"
595 ) ) {
596 if( ! $page_id = wpforo_get_shortcode_pageid( get_option( 'page_on_front' ) ) ) {
597 $wpforo_page = [
598 'post_date' => current_time( 'mysql', 1 ),
599 'post_date_gmt' => current_time( 'mysql', 1 ),
600 'post_content' => '[wpforo]',
601 'post_title' => 'Forum',
602 'post_status' => 'publish',
603 'comment_status' => 'close',
604 'ping_status' => 'close',
605 'post_name' => 'community',
606 'post_modified' => current_time( 'mysql', 1 ),
607 'post_modified_gmt' => current_time( 'mysql', 1 ),
608 'post_parent' => 0,
609 'menu_order' => 0,
610 'post_type' => 'page',
611 ];
612 $page_id = wp_insert_post( $wpforo_page );
613 }
614 if( $page_id && ! is_wp_error( $page_id ) ) {
615 wpforo_update_option( 'wpforo_pageid', $page_id );
616 }
617 }
618
619 flush_rewrite_rules( false );
620 nocache_headers();
621 }
622
623 function wpforo_repair_main_shortcode_page() {
624 $pageid = WPF()->board->get_current( 'pageid' );
625 $sql = "SELECT `ID` FROM `" . WPF()->db->posts . "`
626 WHERE `ID` = " . $pageid . "
627 AND `post_content` LIKE '%[wpforo%'
628 AND `post_status` LIKE 'publish'
629 AND `post_type` IN('" . implode( "','", wpforo_get_blog_content_types() ) . "')";
630 if( ! $pageid || ! WPF()->db->get_var( $sql ) ) {
631 wpforo_create_forum_page();
632 } else {
633 flush_rewrite_rules( false );
634 nocache_headers();
635 }
636 }
637
638 function wpforo_import_default_board() {
639 if( ! WPF()->board->get_current( 'boardid' ) && ! WPF()->board->_get_board( 0 ) ) {
640 $blogname = get_option( 'blogname', '' );
641 $_general = wpforo_get_option( 'wpforo_general_options', [
642 'title' => $blogname . ' ' . __( 'Forum', 'wpforo' ),
643 'description' => $blogname . ' ' . __( 'Discussion Board', 'wpforo' ),
644 'lang' => 1,
645 ], false );
646 $board = WPF()->board->get_current();
647 $board['slug'] = basename( trim( (string) wpforo_get_option( 'wpforo_permastruct', 'community', false ), '/' ) );
648 $board['is_standalone'] = wpforo_get_option( 'wpforo_use_home_url', false );
649 $board['excld_urls'] = array_filter( array_map( 'trim', explode( PHP_EOL, (string) wpforo_get_option( 'wpforo_excld_urls', '' ) ) ) );
650 $board['settings'] = [];
651 $board['settings']['title'] = $_general['title'];
652 $board['settings']['desc'] = $_general['description'];
653 if( $boardid = WPF()->board->add( $board ) ) {
654 WPF()->board->edit( [ 'boardid' => 0 ], $boardid );
655 }
656 }
657 }
658
659 function wpforo_import_default_menus() {
660 $boardid = WPF()->board->get_current( 'boardid' );
661 $menu_name = wpforo_phrase( 'wpForo Navigation', false, 'orig' ) . ( $boardid ? ' #' . $boardid : '' );
662 $menu_location = wpforo_prefix_slug( 'menu' );
663 $menu_exists = wp_get_nav_menu_object( $menu_name );
664 if( ! $menu_exists ) {
665 $id = [];
666 $menu_id = wp_create_nav_menu( $menu_name );
667 $id['wpforo-home'] = wp_update_nav_menu_item( $menu_id, 0, [
668 'menu-item-title' => wpforo_phrase( 'Forums', false ),
669 'menu-item-classes' => 'wpforo-home',
670 'menu-item-url' => '/%wpforo-home%/',
671 'menu-item-status' => 'publish',
672 'menu-item-parent-id' => 0,
673 'menu-item-position' => 0,
674 ] );
675
676 $id['wpforo-recent-activity'] = wp_update_nav_menu_item( $menu_id, 0, [
677 'menu-item-title' => wpforo_phrase( 'What\'s New', false ),
678 'menu-item-classes' => 'wpforo-whats-new',
679 'menu-item-url' => '/%wpforo-recent-activity%/',
680 'menu-item-status' => 'publish',
681 'menu-item-parent-id' => 0,
682 'menu-item-position' => 0,
683 ] );
684
685 $id['wpforo-recent'] = wp_update_nav_menu_item( $menu_id, 0, [
686 'menu-item-title' => wpforo_phrase( 'Recent Posts', false ),
687 'menu-item-classes' => 'wpforo-recent',
688 'menu-item-url' => '/%wpforo-recent%/',
689 'menu-item-status' => 'publish',
690 'menu-item-parent-id' => 0,
691 'menu-item-position' => 0,
692 ] );
693
694 $id['wpforo-members'] = wp_update_nav_menu_item( $menu_id, 0, [
695 'menu-item-title' => wpforo_phrase( 'Members', false ),
696 'menu-item-classes' => 'wpforo-members',
697 'menu-item-url' => '/%wpforo-members%/',
698 'menu-item-status' => 'publish',
699 'menu-item-parent-id' => 0,
700 'menu-item-position' => 0,
701 ] );
702
703 $id['wpforo-profile'] = wp_update_nav_menu_item( $menu_id, 0, [
704 'menu-item-title' => wpforo_phrase( 'My Profile', false ),
705 'menu-item-classes' => 'wpforo-profile',
706 'menu-item-url' => '/%wpforo-profile-home%/',
707 'menu-item-status' => 'publish',
708 'menu-item-parent-id' => 0,
709 'menu-item-position' => 0,
710 ] );
711
712 if( isset( $id['wpforo-profile'] ) && $id['wpforo-profile'] ) {
713 $id['wpforo-profile-account'] = wp_update_nav_menu_item( $menu_id, 0, [
714 'menu-item-title' => wpforo_phrase( 'Account', false ),
715 'menu-item-classes' => 'wpforo-profile-account',
716 'menu-item-url' => '/%wpforo-profile-account%/',
717 'menu-item-status' => 'publish',
718 'menu-item-parent-id' => $id['wpforo-profile'],
719 'menu-item-position' => 1,
720 ] );
721 $id['wpforo-profile-activity'] = wp_update_nav_menu_item( $menu_id, 0, [
722 'menu-item-title' => wpforo_phrase( 'Activity', false ),
723 'menu-item-classes' => 'wpforo-profile-activity',
724 'menu-item-url' => '/%wpforo-profile-activity%/',
725 'menu-item-status' => 'publish',
726 'menu-item-parent-id' => $id['wpforo-profile'],
727 'menu-item-position' => 1,
728 ] );
729 $id['wpforo-profile-subscriptions'] = wp_update_nav_menu_item( $menu_id, 0, [
730 'menu-item-title' => wpforo_phrase( 'Subscriptions', false ),
731 'menu-item-classes' => 'wpforo-profile-subscriptions',
732 'menu-item-url' => '/%wpforo-profile-subscriptions%/',
733 'menu-item-status' => 'publish',
734 'menu-item-parent-id' => $id['wpforo-profile'],
735 'menu-item-position' => 2,
736 ] );
737 }
738
739 $id['wpforo-register'] = wp_update_nav_menu_item( $menu_id, 0, [
740 'menu-item-title' => wpforo_phrase( 'Register', false ),
741 'menu-item-classes' => 'wpforo-register',
742 'menu-item-url' => '/%wpforo-register%/',
743 'menu-item-status' => 'publish',
744 'menu-item-parent-id' => 0,
745 'menu-item-position' => 0,
746 ] );
747
748 $id['wpforo-login'] = wp_update_nav_menu_item( $menu_id, 0, [
749 'menu-item-title' => wpforo_phrase( 'Login', false ),
750 'menu-item-classes' => 'wpforo-login',
751 'menu-item-url' => '/%wpforo-login%/',
752 'menu-item-status' => 'publish',
753 'menu-item-parent-id' => 0,
754 'menu-item-position' => 0,
755 ] );
756
757 $id['wpforo-logout'] = wp_update_nav_menu_item( $menu_id, 0, [
758 'menu-item-title' => wpforo_phrase( 'Logout', false ),
759 'menu-item-classes' => 'wpforo-logout',
760 'menu-item-url' => '/%wpforo-logout%/',
761 'menu-item-status' => 'publish',
762 'menu-item-parent-id' => 0,
763 'menu-item-position' => 0,
764 ] );
765
766 if( ! has_nav_menu( $menu_location ) ) {
767 $locations = get_theme_mod( 'nav_menu_locations' );
768 if( empty( $locations ) ) $locations = [];
769 $locations[ $menu_location ] = $menu_id;
770 set_theme_mod( 'nav_menu_locations', $locations );
771 }
772 }
773 }
774
775 function wpforo_import_default_accesses() {
776 $cans_n = [
777 'vf' => 0,
778 'enf' => 0,
779 'ct' => 0,
780 'vt' => 0,
781 'ent' => 0,
782 'et' => 0,
783 'dt' => 0,
784 'cr' => 0,
785 'ocr' => 0,
786 'vr' => 0,
787 'er' => 0,
788 'dr' => 0,
789 'tag' => 0,
790 'eot' => 0,
791 'eor' => 0,
792 'dot' => 0,
793 'dor' => 0,
794 'sb' => 0,
795 'l' => 0,
796 'r' => 0,
797 's' => 0,
798 'au' => 0,
799 'p' => 0,
800 'op' => 0,
801 'vp' => 0,
802 'sv' => 0,
803 'osv' => 0,
804 'v' => 0,
805 'vop' => 0,
806 'vlp' => 0,
807 'a' => 0,
808 'va' => 0,
809 'at' => 0,
810 'oat' => 0,
811 'aot' => 0,
812 'cot' => 0,
813 'mt' => 0,
814 // Poll-related permissions moved to wpForo Polls plugin
815 ];
816 $cans_r = [
817 'vf' => 1,
818 'enf' => 1,
819 'ct' => 0,
820 'vt' => 1,
821 'ent' => 1,
822 'et' => 0,
823 'dt' => 0,
824 'cr' => 0,
825 'ocr' => 0,
826 'vr' => 1,
827 'er' => 0,
828 'dr' => 0,
829 'tag' => 0,
830 'eot' => 0,
831 'eor' => 0,
832 'dot' => 0,
833 'dor' => 0,
834 'sb' => 1,
835 'l' => 0,
836 'r' => 0,
837 's' => 0,
838 'au' => 0,
839 'p' => 0,
840 'op' => 0,
841 'vp' => 0,
842 'sv' => 0,
843 'osv' => 0,
844 'v' => 0,
845 'vop' => 0,
846 'vlp' => 1,
847 'a' => 0,
848 'va' => 1,
849 'at' => 0,
850 'oat' => 0,
851 'aot' => 0,
852 'cot' => 0,
853 'mt' => 0,
854 // Poll-related permissions moved to wpForo Polls plugin
855 ];
856 $cans_s = [
857 'vf' => 1,
858 'enf' => 1,
859 'ct' => 1,
860 'vt' => 1,
861 'ent' => 1,
862 'et' => 0,
863 'dt' => 0,
864 'cr' => 1,
865 'ocr' => 1,
866 'vr' => 1,
867 'er' => 0,
868 'dr' => 0,
869 'tag' => 1,
870 'eot' => 1,
871 'eor' => 1,
872 'dot' => 1,
873 'dor' => 1,
874 'sb' => 1,
875 'l' => 1,
876 'r' => 1,
877 's' => 0,
878 'au' => 0,
879 'p' => 0,
880 'op' => 1,
881 'vp' => 0,
882 'sv' => 0,
883 'osv' => 1,
884 'v' => 1,
885 'vop' => 1,
886 'vlp' => 1,
887 'a' => 1,
888 'va' => 1,
889 'at' => 0,
890 'oat' => 1,
891 'aot' => 1,
892 'cot' => 0,
893 'mt' => 0,
894 // Poll-related permissions moved to wpForo Polls plugin
895 ];
896 $cans_m = [
897 'vf' => 1,
898 'enf' => 1,
899 'ct' => 1,
900 'vt' => 1,
901 'ent' => 1,
902 'et' => 1,
903 'dt' => 1,
904 'cr' => 1,
905 'ocr' => 1,
906 'vr' => 1,
907 'er' => 1,
908 'dr' => 1,
909 'tag' => 1,
910 'eot' => 1,
911 'eor' => 1,
912 'dot' => 1,
913 'dor' => 1,
914 'sb' => 1,
915 'l' => 1,
916 'r' => 1,
917 's' => 1,
918 'au' => 1,
919 'p' => 1,
920 'op' => 1,
921 'vp' => 1,
922 'sv' => 1,
923 'osv' => 1,
924 'v' => 1,
925 'vop' => 1,
926 'vlp' => 1,
927 'a' => 1,
928 'va' => 1,
929 'at' => 1,
930 'oat' => 1,
931 'aot' => 1,
932 'cot' => 1,
933 'mt' => 1,
934 // Poll-related permissions moved to wpForo Polls plugin
935 ];
936 $cans_a = [
937 'vf' => 1,
938 'enf' => 1,
939 'ct' => 1,
940 'vt' => 1,
941 'ent' => 1,
942 'et' => 1,
943 'dt' => 1,
944 'cr' => 1,
945 'ocr' => 1,
946 'vr' => 1,
947 'er' => 1,
948 'dr' => 1,
949 'tag' => 1,
950 'eot' => 1,
951 'eor' => 1,
952 'dot' => 1,
953 'dor' => 1,
954 'sb' => 1,
955 'l' => 1,
956 'r' => 1,
957 's' => 1,
958 'au' => 1,
959 'p' => 1,
960 'op' => 1,
961 'vp' => 1,
962 'sv' => 1,
963 'osv' => 1,
964 'v' => 1,
965 'vop' => 1,
966 'vlp' => 1,
967 'a' => 1,
968 'va' => 1,
969 'at' => 1,
970 'oat' => 1,
971 'aot' => 1,
972 'cot' => 1,
973 'mt' => 1,
974 // Poll-related permissions moved to wpForo Polls plugin
975 ];
976
977 //Add new Accesses in this array to add those in custom Accesses created by forum admin
978 $cans_default = [
979 'sb' => 1,
980 'au' => 1,
981 'p' => 0,
982 'op' => 1,
983 'vp' => 0,
984 // Poll-related permissions moved to wpForo Polls plugin
985 'aot' => 1,
986 'tag' => 1,
987 'ocr' => 0,
988 'enf' => 1,
989 'ent' => 1,
990 'vop' => 1,
991 'vlp' => 1,
992 ];
993
994 $sql = "SELECT * FROM `" . WPF()->tables->accesses . "`";
995 $accesses = WPF()->db->get_results( $sql, ARRAY_A );
996 if( empty( $accesses ) ) {
997 $cans_n = serialize( $cans_n );
998 $cans_r = serialize( $cans_r );
999 $cans_s = serialize( $cans_s );
1000 $cans_m = serialize( $cans_m );
1001 $cans_a = serialize( $cans_a );
1002
1003 $sql = "INSERT IGNORE INTO `" . WPF()->tables->accesses . "`
1004 (`access`, `title`, cans) VALUES
1005 ('no_access', 'No access', '" . $cans_n . "'),
1006 ('read_only', 'Read only access', '" . $cans_r . "'),
1007 ('standard', 'Standard access', '" . $cans_s . "'),
1008 ('moderator', 'Moderator access', '" . $cans_m . "'),
1009 ('full', 'Full access', '" . $cans_a . "')";
1010
1011 WPF()->db->query( $sql );
1012 } else {
1013 foreach( $accesses as $access ) {
1014 $current = unserialize( $access['cans'] );
1015 if( strtolower( (string) $access['access'] ) === 'no_access' ) {
1016 $default = $cans_n;
1017 } elseif( strtolower( (string) $access['access'] ) === 'read_only' ) {
1018 $default = $cans_r;
1019 } elseif( strtolower( (string) $access['access'] ) === 'standard' ) {
1020 $default = $cans_s;
1021 } elseif( strtolower( (string) $access['access'] ) === 'moderator' ) {
1022 $default = $cans_m;
1023 } elseif( strtolower( (string) $access['access'] ) === 'full' ) {
1024 $default = $cans_a;
1025 } else {
1026 $default = $cans_default;
1027 }
1028 if( ! empty( $default ) ) {
1029 $data_update = array_merge( $default, $current );
1030 if( ! empty( $data_update ) ) {
1031 $data_update = serialize( $data_update );
1032 WPF()->db->query( "UPDATE `" . WPF()->tables->accesses . "` SET `cans` = '" . WPF()->db->_real_escape( $data_update ) . "' WHERE `accessid` = " . intval( $access['accessid'] ) );
1033 }
1034 }
1035 }
1036 }
1037 }
1038
1039 function wpforo_import_default_usergroups() {
1040 $cans_admin = [
1041 'mf' => 1,
1042 'mai' => 1,
1043 'ms' => 1,
1044 'mt' => 1,
1045 'mp' => 1,
1046 'mth' => 1,
1047 'vm' => 1,
1048 'aum' => 1,
1049 'em' => 1,
1050 'vmg' => 1,
1051 'aup' => 1,
1052 'vmem' => 1,
1053 'view_stat' => 1,
1054 'vprf' => 1,
1055 'vpra' => 1,
1056 'vprs' => 1,
1057 'bm' => 1,
1058 'dm' => 1,
1059 'upc' => 1,
1060 'upa' => 1,
1061 'ups' => 1,
1062 'va' => 1,
1063 'vmu' => 1,
1064 'vmm' => 1,
1065 'vmt' => 1,
1066 'vmct' => 1,
1067 'vmr' => 1,
1068 'vmw' => 1,
1069 'vmsn' => 1,
1070 'vmrd' => 1,
1071 'vml' => 1,
1072 'vmo' => 1,
1073 'vms' => 1,
1074 'vmam' => 1,
1075 'vwpm' => 1,
1076 'caa' => 1,
1077 'vt_add_topic' => 1,
1078 'ai_search' => 1,
1079 'ai_summary' => 1,
1080 'ai_translation' => 1,
1081 'ai_suggestion' => 1,
1082 ];
1083 $cans_moder = [
1084 'mf' => 0,
1085 'ms' => 0,
1086 'mai' => 0,
1087 'mt' => 0,
1088 'mp' => 0,
1089 'mth' => 0,
1090 'vm' => 0,
1091 'aum' => 1,
1092 'em' => 0,
1093 'vmg' => 0,
1094 'aup' => 1,
1095 'vmem' => 1,
1096 'view_stat' => 1,
1097 'vprf' => 1,
1098 'vpra' => 1,
1099 'vprs' => 1,
1100 'bm' => 1,
1101 'dm' => 1,
1102 'upc' => 1,
1103 'upa' => 1,
1104 'ups' => 1,
1105 'va' => 1,
1106 'vmu' => 0,
1107 'vmm' => 1,
1108 'vmt' => 1,
1109 'vmct' => 1,
1110 'vmr' => 1,
1111 'vmw' => 1,
1112 'vmsn' => 1,
1113 'vmrd' => 1,
1114 'vml' => 1,
1115 'vmo' => 1,
1116 'vms' => 1,
1117 'vmam' => 1,
1118 'vwpm' => 1,
1119 'caa' => 1,
1120 'vt_add_topic' => 1,
1121 'ai_search' => 1,
1122 'ai_summary' => 1,
1123 'ai_translation' => 1,
1124 'ai_suggestion' => 1,
1125 ];
1126 $cans_reg = [
1127 'mf' => 0,
1128 'ms' => 0,
1129 'mai' => 0,
1130 'mt' => 0,
1131 'mp' => 0,
1132 'mth' => 0,
1133 'vm' => 0,
1134 'aum' => 0,
1135 'em' => 0,
1136 'vmg' => 0,
1137 'aup' => 1,
1138 'vmem' => 1,
1139 'view_stat' => 1,
1140 'vprf' => 1,
1141 'vpra' => 1,
1142 'vprs' => 0,
1143 'bm' => 0,
1144 'dm' => 0,
1145 'upc' => 1,
1146 'upa' => 1,
1147 'ups' => 1,
1148 'va' => 1,
1149 'vmu' => 0,
1150 'vmm' => 0,
1151 'vmt' => 1,
1152 'vmct' => 1,
1153 'vmr' => 1,
1154 'vmw' => 1,
1155 'vmsn' => 1,
1156 'vmrd' => 1,
1157 'vml' => 1,
1158 'vmo' => 1,
1159 'vms' => 1,
1160 'vmam' => 1,
1161 'vwpm' => 1,
1162 'caa' => 1,
1163 'vt_add_topic' => 1,
1164 'ai_search' => 1,
1165 'ai_summary' => 1,
1166 'ai_translation' => 1,
1167 'ai_suggestion' => 1,
1168 ];
1169 $cans_guest = [
1170 'mf' => 0,
1171 'ms' => 0,
1172 'mai' => 0,
1173 'mt' => 0,
1174 'mp' => 0,
1175 'mth' => 0,
1176 'vm' => 0,
1177 'aum' => 0,
1178 'em' => 0,
1179 'vmg' => 0,
1180 'aup' => 0,
1181 'vmem' => 1,
1182 'view_stat' => 1,
1183 'vprf' => 1,
1184 'vpra' => 1,
1185 'vprs' => 0,
1186 'bm' => 0,
1187 'dm' => 0,
1188 'upc' => 0,
1189 'upa' => 0,
1190 'ups' => 0,
1191 'va' => 1,
1192 'vmu' => 0,
1193 'vmm' => 0,
1194 'vmt' => 1,
1195 'vmct' => 1,
1196 'vmr' => 1,
1197 'vmw' => 0,
1198 'vmsn' => 1,
1199 'vmrd' => 1,
1200 'vml' => 1,
1201 'vmo' => 1,
1202 'vms' => 1,
1203 'vmam' => 1,
1204 'vwpm' => 0,
1205 'caa' => 1,
1206 'vt_add_topic' => 0,
1207 'ai_search' => 1,
1208 'ai_summary' => 1,
1209 'ai_translation' => 1,
1210 'ai_suggestion' => 0,
1211 ];
1212 $cans_customer = [
1213 'mf' => 0,
1214 'ms' => 0,
1215 'mai' => 0,
1216 'mt' => 0,
1217 'mp' => 0,
1218 'mth' => 0,
1219 'vm' => 0,
1220 'aum' => 0,
1221 'em' => 0,
1222 'vmg' => 0,
1223 'aup' => 0,
1224 'vmem' => 1,
1225 'view_stat' => 1,
1226 'vprf' => 1,
1227 'vpra' => 1,
1228 'vprs' => 0,
1229 'bm' => 0,
1230 'dm' => 0,
1231 'upc' => 1,
1232 'upa' => 1,
1233 'ups' => 1,
1234 'va' => 1,
1235 'vmu' => 0,
1236 'vmm' => 0,
1237 'vmt' => 1,
1238 'vmct' => 1,
1239 'vmr' => 1,
1240 'vmw' => 1,
1241 'vmsn' => 1,
1242 'vmrd' => 1,
1243 'vml' => 1,
1244 'vmo' => 1,
1245 'vms' => 1,
1246 'vmam' => 1,
1247 'vwpm' => 1,
1248 'caa' => 1,
1249 'vt_add_topic' => 1,
1250 'ai_search' => 1,
1251 'ai_summary' => 1,
1252 'ai_translation' => 1,
1253 'ai_suggestion' => 1,
1254 ];
1255
1256 //Add new Cans in this array to add those in custom Usergroup created by forum admin
1257 $cans_defaults = [
1258 'mf' => 0,
1259 'ms' => 0,
1260 'mai' => 0,
1261 'mt' => 0,
1262 'mp' => 0,
1263 'mth' => 0,
1264 'vmem' => 1,
1265 'view_stat' => 1,
1266 'vprf' => 1,
1267 'caa' => 1,
1268 'vt_add_topic' => 1,
1269 'upc' => 1,
1270 'ai_search' => 1,
1271 'ai_summary' => 1,
1272 'ai_translation' => 1,
1273 'ai_suggestion' => 1,
1274 ];
1275
1276 $sql = "SELECT * FROM `" . WPF()->tables->usergroups . "`";
1277 if( ! $usergroups = WPF()->db->get_results( $sql, ARRAY_A ) ) {
1278 WPF()->usergroup->add( 'Admin', $cans_admin, '', 'administrator', 'full', '#FF3333' );
1279 WPF()->usergroup->add( 'Moderator', $cans_moder, '', 'editor', 'moderator', '#0066FF' );
1280 WPF()->usergroup->add( 'Registered', $cans_reg, '', 'subscriber', 'standard', '', 1, 1 );
1281 WPF()->usergroup->add( 'Guest', $cans_guest, '', '', 'read_only', '#222222', 0 );
1282 WPF()->usergroup->add( 'Customer', $cans_customer, '', 'customer', 'standard', '#993366', 1, 1 );
1283 } else {
1284 foreach( $usergroups as $usergroup ) {
1285 $current = unserialize( $usergroup['cans'] );
1286 if( strtolower( (string) $usergroup['name'] ) === 'admin' ) {
1287 $default = $cans_admin;
1288 } elseif( strtolower( (string) $usergroup['name'] ) === 'moderator' ) $default = $cans_moder;
1289 elseif( strtolower( (string) $usergroup['name'] ) === 'registered' ) $default = $cans_reg;
1290 elseif( strtolower( (string) $usergroup['name'] ) === 'guest' ) $default = $cans_guest;
1291 elseif( strtolower( (string) $usergroup['name'] ) === 'customer' ) $default = $cans_customer;
1292 else {
1293 $default = $cans_defaults;
1294 }
1295 if( ! empty( $default ) ) {
1296 $data_update = array_merge( $default, $current );
1297 if( ! empty( $data_update ) ) {
1298 $data_update = serialize( $data_update );
1299 WPF()->db->query(
1300 "UPDATE `" . WPF()->tables->usergroups . "` SET `cans` = '" . WPF()->db->_real_escape( $data_update ) . "' WHERE `groupid` = " . intval( $usergroup['groupid'] )
1301 );
1302 }
1303 }
1304 }
1305 }
1306 }
1307
1308 function wpforo_import_default_forums() {
1309 $sql = "SELECT COUNT(*) FROM `" . WPF()->tables->forums . "`";
1310 $count = WPF()->db->get_var( $sql );
1311 if( ! $count ) {
1312 if( $parentid = WPF()->forum->add(
1313 [ 'title' => __( 'Main Category', 'wpforo' ), 'description' => __( 'This is a simple category / section', 'wpforo' ), 'layout' => 4, 'icon' => 'fas fa-comments' ],
1314 false
1315 ) ) {
1316 WPF()->forum->add(
1317 [ 'title' => __( 'Main Forum', 'wpforo' ), 'description' => __( 'This is a simple parent forum', 'wpforo' ), 'parentid' => $parentid, 'layout' => 4, 'icon' => 'fas fa-comments' ],
1318 false
1319 );
1320 }
1321 }
1322 }
1323
1324 function wpforo_fix_wp_permalink_structure() {
1325 $permalink_structure = get_option( 'permalink_structure' );
1326 if( ! $permalink_structure ) {
1327 global $wp_rewrite;
1328 $wp_rewrite->set_permalink_structure( '/%postname%/' );
1329 }
1330 }
1331
1332 function wpforo_fix_installed_options() {
1333 /**
1334 * in the update process, using old features settings set the right profile setting for the new option
1335 */
1336 if( $features = wpforo_get_option( 'features', [], false ) ) {
1337 if( ! (int) wpfval( $features, 'profile' ) ) {
1338 if( (int) wpfval( $features, 'bp_profile' ) && class_exists( 'BP_Component' ) ) {
1339 $features['profile'] = 3;
1340 } elseif( (int) wpfval( $features, 'um_profile' ) && function_exists( 'UM' ) ) {
1341 $features['profile'] = 4;
1342 } elseif( (int) wpfval( $features, 'comment-author-link' ) ) {
1343 $features['profile'] = 2;
1344 } else {
1345 $features['profile'] = 1;
1346 }
1347 wpforo_update_option( 'features', array_map( 'intval', $features ) );
1348 }
1349 }
1350
1351 #################################################################
1352 // CHECK Addon Notice /////////////////////////////////////////
1353 $lastHash = get_option( 'wpforo_addon_note_dismissed' );
1354 $first = get_option( 'wpforo_addon_note_first' );
1355 if( $lastHash && $first === 'true' ) {
1356 update_option( 'wpforo_addon_note_first', 'false' );
1357 }
1358
1359 #################################################################
1360 // AVOID PLUGIN CONFLICTS ///////////////////////////////////////
1361 /* Autoptimize *************************************************/
1362 $autopt = get_option( 'autoptimize_js_exclude' );
1363 if( $autopt && strpos( (string) $autopt, 'wp-includes/js/tinymce' ) === false ) {
1364 $autopt = $autopt . ', wp-includes/js/tinymce';
1365 update_option( 'autoptimize_js_exclude', $autopt );
1366 if( class_exists( 'autoptimizeCache' ) && is_callable( [ 'autoptimizeCache', 'clearall' ] ) ) {
1367 autoptimizeCache::clearall();
1368 }
1369 }
1370
1371 #################################################################
1372 // Adding #wpforo to custom css /////////////////////////////////
1373 if( $style_options = wpforo_get_option( 'style_options', [], false ) ) {
1374 $custom_css = wpfval( $style_options, 'custom_css' );
1375 if( $custom_css && strpos( (string) $custom_css, '#wpforo #wpforo-wrap' ) === false ) {
1376 $style_options['custom_css'] = str_replace( '#wpforo-wrap', '#wpforo #wpforo-wrap', $style_options['custom_css'] );
1377 wpforo_update_option( 'style_options', $style_options );
1378 }
1379 }
1380 }
1381
1382 function wpforo_update_theme_options() {
1383 if( $current_theme = wpforo_get_option( 'theme_options_' . WPF()->tpl->theme, [], false ) ) {
1384 $theme = WPF()->tpl->find_theme( '2026' );
1385 if( wpfval( $theme, 'layouts' ) ) {
1386 $current_theme['layouts'] = $theme['layouts'];
1387 $theme = wpforo_deep_merge( $theme, $current_theme );
1388 wpforo_update_option( 'theme_options_' . WPF()->tpl->theme, $theme );
1389 }
1390 }
1391 }
1392
1393 function wpforo_clean_up() {
1394 WPF()->db->delete( WPF()->tables->topics, [ 'first_postid' => 0 ], [ '%d' ] );
1395 }
1396
1397 // wpforo database checker fixer tools
1398 function wpforo_update_db() {
1399 $problems = wpforo_database_check();
1400 if( ! empty( $problems ) ) {
1401 $SQL = wpforo_database_fixer( $problems );
1402 if( wpfval( $SQL, 'fields' ) ) {
1403 foreach( $SQL['fields'] as $query ) WPF()->db->query( $query );
1404 }
1405 if( wpfval( $SQL, 'keys' ) ) {
1406 foreach( $SQL['keys'] as $query ) WPF()->db->query( $query );
1407 }
1408 if( wpfval( $SQL, 'tables' ) ) {
1409 foreach( $SQL['tables'] as $query ) {
1410 if( false === WPF()->db->query( $query ) ) {
1411 WPF()->db->query( preg_replace( '#\)[\r\n\t\s]*ENGINE.*$#isu', ')', $query ) );
1412 }
1413 }
1414 }
1415 if( wpfval( $SQL, 'data' ) ) {
1416 foreach( $SQL['data'] as $query ) {
1417 WPF()->db->query( $query );
1418 }
1419 }
1420 }
1421 wpforo_update_option( 'version_db', WPFORO_VERSION );
1422 }
1423
1424 /**
1425 * @param array $args
1426 *
1427 * @return bool|string|null
1428 */
1429 function wpforo_db_check( $args = [] ) {
1430 $key = [ 'wpforo_db_check', $args ];
1431 if( WPF()->ram_cache->exists( $key ) ) return WPF()->ram_cache->get( $key );
1432
1433 global $wpdb;
1434
1435 $col = esc_sql( trim( (string) wpfval( $args, 'col' ) ) );
1436 $table = esc_sql( trim( (string) wpfval( $args, 'table' ) ) );
1437
1438 $result = null;
1439 switch( trim( (string) wpfval( $args, 'check' ) ) ) {
1440 case 'table_exists':
1441 $result = (bool) $wpdb->get_var( "SHOW TABLES LIKE '$table'" );
1442 break;
1443 case 'col_exists':
1444 $result = (bool) $wpdb->get_var( "SHOW COLUMNS FROM `$table` LIKE '$col'" );
1445 break;
1446 case 'key_exists':
1447 $result = (bool) $wpdb->get_var( "SHOW KEYS FROM `$table` WHERE `Key_name` = '$col'" );
1448 break;
1449 case 'default_value':
1450 $c = (array) $wpdb->get_row( "SHOW COLUMNS FROM `$table` LIKE '$col'", ARRAY_A );
1451 $result = wpfval( $c, 'Default' );
1452 break;
1453 case 'col_type':
1454 $c = (array) $wpdb->get_row( "SHOW COLUMNS FROM `$table` LIKE '$col'", ARRAY_A );
1455 $result = wpfval( $c, 'Type' );
1456 break;
1457 }
1458
1459 WPF()->ram_cache->set( $key, $result );
1460
1461 return $result;
1462 }
1463
1464 function wpforo_database_check() {
1465 $_tables = [];
1466 $_table_diffs = [];
1467 require_once( WPFORO_DIR . '/includes/install-sql.php' );
1468 $wpforo_sql = wpforo_get_install_sqls();
1469 if( ! empty( $wpforo_sql ) ) {
1470 foreach( $wpforo_sql as $sql ) {
1471 if( preg_match( '|EXISTS \`([^\(]+)\`\s*\((.+)(PRIMARY.+)\)\s*ENGINE|is', $sql, $table ) ) {
1472 if( wpfval( $table, 1 ) ) {
1473 if( wpfval( $table, 2 ) ) {
1474 if( preg_match_all( '|\`([^\`]+)\`|is', $table[2], $fields, PREG_SET_ORDER ) ) {
1475 foreach( $fields as $field ) {
1476 if( wpfval( $field, 1 ) ) $_tables[ $table[1] ]['fields'][] = $field[1];
1477 }
1478 }
1479 }
1480 if( wpfval( $table, 3 ) ) {
1481 if( preg_match( '|PRIMARY KEY \(\`([^\`]+)\`\)|is', $table[3], $primary_key ) ) {
1482 $_tables[ $table[1] ]['keys'][] = $primary_key[1];
1483 }
1484 if( preg_match_all( '|KEY \`([^\`]+)\`|is', $table[3], $keys, PREG_SET_ORDER ) ) {
1485 foreach( $keys as $key ) {
1486 if( wpfval( $key, 1 ) ) $_tables[ $table[1] ]['keys'][] = $key[1];
1487 }
1488 }
1489 }
1490 }
1491 }
1492 }
1493 if( ! empty( $_tables ) ) {
1494 foreach( $_tables as $_name => $_structure ) {
1495 $_table_fields = [];
1496 $_table_keys = [];
1497 $_table_exists = WPF()->db->get_var( "SHOW TABLES LIKE '" . esc_sql( $_name ) . "'" );
1498 if( $_table_exists ) {
1499 //Problems - Missing Field
1500 if( wpfval( $_structure, 'fields' ) ) {
1501 $_fields = WPF()->db->get_results( "SHOW FULL COLUMNS FROM " . esc_sql( $_name ), ARRAY_A );
1502 foreach( $_fields as $_field ) $_table_fields[] = $_field['Field'];
1503 $_count_orig = count( $_structure['fields'] );
1504 $_count_curr = count( $_table_fields );
1505 if( $_count_curr < $_count_orig ) {
1506 $diff = array_diff( $_structure['fields'], $_table_fields );
1507 if( ! empty( $diff ) ) $_table_diffs[ $_name ]['fields'][ $_name ] = $diff;
1508 }
1509 }
1510 //Problems - Missing Key
1511 if( wpfval( $_structure, 'keys' ) ) {
1512 $_keys = WPF()->db->get_results( "SHOW KEYS FROM " . esc_sql( $_name ), ARRAY_A );
1513 foreach( $_keys as $_key ) {
1514 if( strpos( (string) $_key['Key_name'], 'PRIMARY' ) !== false ) {
1515 $_table_keys[] = $_key['Column_name'];
1516 } else {
1517 $_table_keys[] = $_key['Key_name'];
1518 }
1519 }
1520 $_table_keys = array_unique( $_table_keys );
1521 $_table_keys = array_values( $_table_keys );
1522 $_count_orig = count( $_structure['keys'] );
1523 $_count_curr = count( $_table_keys );
1524 if( $_count_curr < $_count_orig ) {
1525 $diff_keys = array_diff( $_structure['keys'], $_table_keys );
1526 if( ! empty( $diff_keys ) ) $_table_diffs[ $_name ]['keys'][ $_name ] = $diff_keys;
1527 }
1528 }
1529 } else {
1530 //Problems - Missing Table
1531 $_table_diffs[ $_name ]['exists'] = 'no';
1532 }
1533 }
1534 }
1535 }
1536
1537 $first_board = WPF()->db->get_row( "SELECT `boardid` FROM `" . WPF()->tables->boards . "` WHERE `boardid` = 0", ARRAY_A );
1538 if( empty( $first_board ) ) {
1539 $_table_diffs['data']['default_board'] = 'no';
1540 }
1541
1542 return $_table_diffs;
1543 }
1544
1545 function wpforo_database_parse() {
1546 $_tables = [];
1547 $_table_diffs = [];
1548 require_once( WPFORO_DIR . '/includes/install-sql.php' );
1549 $wpforo_sql = wpforo_get_install_sqls();
1550 if( ! empty( $wpforo_sql ) ) {
1551 foreach( $wpforo_sql as $sql ) {
1552 if( preg_match( '|EXISTS \`([^\(]+)\`\s*\((.+)(PRIMARY.+)\)\s*ENGINE|is', $sql, $table ) ) {
1553 if( wpfval( $table, 1 ) ) {
1554 if( wpfval( $table, 2 ) ) {
1555 $_tables[ $table[1] ]['fields'] = array_map( 'trim', explode( ',', $table[2] ) );
1556 }
1557 if( wpfval( $table, 3 ) ) {
1558 $_tables[ $table[1] ]['keys'] = array_map( 'trim', explode( PHP_EOL, $table[3] ) );
1559 }
1560 }
1561 }
1562 }
1563 }
1564
1565 return $_tables;
1566 }
1567
1568 function wpforo_database_fixer( $problems ) {
1569 $SQL = [];
1570 if( ! empty( $problems ) ) {
1571 require_once( WPFORO_DIR . '/includes/install-sql.php' );
1572 $table_structure = wpforo_database_parse();
1573 if( ! empty( $table_structure ) ) {
1574 foreach( $problems as $table_name => $problem ) {
1575 if( wpfval( $problem, 'fields' ) ) {
1576 foreach( $problem['fields'] as $problem_fields ) {
1577 if( ! empty( $problem_fields ) ) {
1578 foreach( $problem_fields as $problem_field ) {
1579 if( wpfval( $table_structure, $table_name, 'fields' ) ) {
1580 foreach( $table_structure[ $table_name ]['fields'] as $field_sql ) {
1581 if( strpos( (string) $field_sql, '`' . $problem_field . '`' ) !== false ) {
1582 $SQL['fields'][] = 'ALTER TABLE `' . $table_name . '` ADD ' . $field_sql . ';';
1583 }
1584 }
1585 }
1586 }
1587 }
1588 }
1589 }
1590 if( wpfval( $problem, 'keys' ) ) {
1591 foreach( $problem['keys'] as $problem_keys ) {
1592 if( ! empty( $problem_keys ) ) {
1593 foreach( $problem_keys as $problem_key ) {
1594 if( wpfval( $table_structure, $table_name, 'keys' ) ) {
1595 foreach( $table_structure[ $table_name ]['keys'] as $key_sql ) {
1596 if( preg_match( '|KEY \`' . $problem_key . '\`|is', (string) $key_sql ) ) {
1597 $SQL['keys'][] = 'ALTER TABLE `' . $table_name . '` ADD ' . trim( (string) $key_sql, ',' ) . ';';
1598 }
1599 }
1600 }
1601 }
1602 }
1603 }
1604 }
1605 if( wpfval( $problem, 'exists' ) ) {
1606 $wpforo_sql = wpforo_get_install_sqls();
1607 if( wpfval( $wpforo_sql, $table_name ) ) {
1608 $SQL['tables'][] = preg_replace( '|\t+|', ' ', $wpforo_sql[ $table_name ] );
1609 }
1610 }
1611 }
1612 }
1613 if( wpfval( $problems, 'data' ) ) {
1614 if( wpfval( $problems, 'data', 'default_board' ) ) {
1615 // Missing default board (ID:0)
1616 $board = [];
1617 $blogname = get_option( 'blogname', '' );
1618 $_general = wpforo_get_option( 'wpforo_general_options', [
1619 'title' => $blogname . ' ' . __( 'Forum', 'wpforo' ),
1620 'description' => $blogname . ' ' . __( 'Discussion Board', 'wpforo' ),
1621 ], false );
1622 $board['title'] = ( wpfval( $_general, 'title' ) ) ?: 'Forum';
1623 $board['settings']['title'] = ( wpfval( $_general, 'title' ) ) ?: 'Forum';
1624 $board['settings']['desc'] = ( wpfval( $_general, 'description' ) ) ?: 'Discussion Board';
1625 $settings = wp_json_encode( $board['settings'] );
1626 WPF()->db->query( "UPDATE `" . WPF()->tables->boards . "` SET `slug` = CONCAT('community-', `boardid`) WHERE `slug` = 'community' AND `boardid` != 0" );
1627 $slug = basename( trim( (string) wpforo_get_option( 'wpforo_permastruct', 'community', false ), '/' ) );
1628 $board['slug'] = ( $slug ) ?: 'community';
1629 $board['locale'] = wpforo_get_site_default_locale();
1630 $board['pageid'] = wpforo_get_option( 'wpforo_pageid', 0 );
1631 $all_modules = array_map( '__return_true', wpforo_get_modules_info() );
1632 $all_addons = array_map( '__return_true', wpforo_get_addons_info() );
1633 $modules = array_merge( $all_modules, $all_addons );
1634 $board['modules'] = wp_json_encode( array_map( function( $a ) { return (bool) intval( $a ); }, $modules ) );
1635 $SQL['data']['pre_board'] = "SET sql_mode='NO_AUTO_VALUE_ON_ZERO';";
1636 $SQL['data']['default_board'] = "INSERT INTO `" . WPF()->tables->boards . "` (`boardid`, `title`, `slug`, `pageid`, `modules`, `locale`, `is_standalone`, `excld_urls`, `status`, `settings`)
1637 VALUES(0, '" . esc_sql( $board['title'] ) . "', '" . esc_sql( $board['slug'] ) . "', " . intval( $board['pageid'] ) . ",
1638 '" . esc_sql( $board['modules'] ) . "',
1639 '" . esc_sql( $board['locale'] ) . "', 0, '[]', 1, '" . esc_sql( $settings ) . "');";
1640 }
1641 //Other data checking here...
1642 }
1643 }
1644
1645 return $SQL;
1646 }
1647
1648 function wpforo_add_unique_key( $table, $primary_key, $unique_key_name = '', $unique_fields = '' ) {
1649
1650 $table = esc_sql( trim( (string) $table ) );
1651 $primary_key = esc_sql( trim( (string) $primary_key ) );
1652 $unique_fields = esc_sql( trim( (string) $unique_fields, ',' ) );
1653 $unique_fields_clean = preg_replace( '|\([^()]+\)|', '', (string) $unique_fields );
1654 $remove_rows = '';
1655 $sql = "SELECT GROUP_CONCAT(`$primary_key`) duplicated_row_ids,
1656 COUNT(*) duplication_count FROM
1657 `$table` GROUP BY $unique_fields_clean HAVING duplication_count > 1";
1658
1659 $rows = WPF()->db->get_results( $sql, ARRAY_A );
1660 if( ! empty( $rows ) ) {
1661 foreach( $rows as $row ) {
1662 $ids = explode( ',', $row['duplicated_row_ids'] );
1663 $ids = array_reverse( $ids );
1664 $ids = array_slice( $ids, 1 );
1665 $remove_rows .= trim( implode( ',', $ids ), ',' ) . ',';
1666 }
1667 $remove_rows = esc_sql( trim( $remove_rows, ',' ) );
1668 if( $remove_rows ) {
1669 WPF()->db->query( "DELETE FROM `$table` WHERE `$primary_key` IN($remove_rows)" );
1670 }
1671 }
1672 $sql = "ALTER TABLE `$table` ADD UNIQUE KEY `$unique_key_name`( $unique_fields )";
1673 WPF()->db->query( $sql );
1674 }
1675
1676 /**
1677 * Add What’s New menu item on plugin update (2.x to 3.x migration only)
1678 * Adds "What’s New" right after the first [Forums] menu item.
1679 * This only runs once during the 2.x to 3.x update.
1680 *
1681 * @since 3.0.0
1682 */
1683 function wpforo_migrate_recent_activity_menu() {
1684 // Only run this migration once (2.x to 3.x update)
1685 if( get_option( 'wpforo_recent_activity_menu_migrated' ) ) {
1686 return;
1687 }
1688
1689 // Get all boards to migrate menus for each
1690 $boards = [];
1691 if( isset( WPF()->board ) && method_exists( WPF()->board, 'get_boards' ) ) {
1692 $boards = WPF()->board->get_boards();
1693 }
1694
1695 // If no boards found or multi-board not initialized, just migrate default board
1696 if( empty( $boards ) ) {
1697 $boards = [ [ 'boardid' => 0 ] ];
1698 }
1699
1700 foreach( $boards as $board ) {
1701 $boardid = intval( wpfval( $board, 'boardid' ) );
1702 $menu_name = wpforo_phrase( 'wpForo Navigation', false, 'orig' ) . ( $boardid ? ' #' . $boardid : '' );
1703 $menu_obj = wp_get_nav_menu_object( $menu_name );
1704
1705 if( ! $menu_obj ) {
1706 continue; // Skip boards without a menu (fresh install will create menu with What's New)
1707 }
1708
1709 $menu_id = $menu_obj->term_id;
1710 $menu_items = wp_get_nav_menu_items( $menu_id );
1711
1712 if( empty( $menu_items ) ) {
1713 continue;
1714 }
1715
1716 // Check if "What's New" already exists (check both old and new CSS class names)
1717 $has_whats_new = false;
1718 foreach( $menu_items as $item ) {
1719 if( is_array( $item->classes ) && ( in_array( 'wpforo-whats-new', $item->classes ) || in_array( 'wpforo-recent-activity', $item->classes ) ) ) {
1720 $has_whats_new = true;
1721 break;
1722 }
1723 }
1724
1725 if( $has_whats_new ) {
1726 continue; // What's New already exists for this board, skip
1727 }
1728
1729 // Find the first [Forums] menu item (wpforo-home) to get its position
1730 $forums_position = 1;
1731 foreach( $menu_items as $item ) {
1732 if( is_array( $item->classes ) && in_array( 'wpforo-home', $item->classes ) ) {
1733 $forums_position = $item->menu_order;
1734 break;
1735 }
1736 }
1737
1738 // Shift all menu items after [Forums] by 1 position
1739 foreach( $menu_items as $item ) {
1740 if( $item->menu_order > $forums_position ) {
1741 wp_update_nav_menu_item( $menu_id, $item->db_id, [
1742 'menu-item-title' => $item->title,
1743 'menu-item-classes' => implode( ' ', (array) $item->classes ),
1744 'menu-item-url' => $item->url,
1745 'menu-item-status' => 'publish',
1746 'menu-item-parent-id' => $item->menu_item_parent,
1747 'menu-item-position' => $item->menu_order + 1,
1748 ] );
1749 }
1750 }
1751
1752 // Add "What's New" right after [Forums]
1753 wp_update_nav_menu_item( $menu_id, 0, [
1754 'menu-item-title' => wpforo_phrase( 'What\'s New', false ),
1755 'menu-item-classes' => 'wpforo-whats-new',
1756 'menu-item-url' => '/%wpforo-recent-activity%/',
1757 'menu-item-status' => 'publish',
1758 'menu-item-parent-id' => 0,
1759 'menu-item-position' => $forums_position + 1,
1760 ] );
1761 }
1762
1763 // Mark migration as complete - won't run again on future updates
1764 update_option( 'wpforo_recent_activity_menu_migrated', 1 );
1765 }
1766
1767 /**
1768 * Migrate theme styles from 2022 to 2026 theme on plugin update (2.x to 3.x migration)
1769 * Preserves user's custom colors and CSS when the default theme changes from 2022 to 2026.
1770 * Supports multi-board installations.
1771 * This only runs once during the 2.x to 3.x update.
1772 *
1773 * @since 3.0.0
1774 */
1775 function wpforo_migrate_theme_styles() {
1776 // Only run this migration once
1777 if( get_option( 'wpforo_theme_styles_migrated' ) ) {
1778 return;
1779 }
1780
1781 // Get all boards to migrate styles for each
1782 $boards = [];
1783 if( isset( WPF()->board ) && method_exists( WPF()->board, 'get_boards' ) ) {
1784 $boards = WPF()->board->get_boards();
1785 }
1786
1787 // If no boards found or multi-board not initialized, just migrate default board
1788 if( empty( $boards ) ) {
1789 $boards = [ [ 'boardid' => 0 ] ];
1790 }
1791
1792 foreach( $boards as $board ) {
1793 $boardid = intval( wpfval( $board, 'boardid' ) );
1794 $prefix = WPF()->generate_prefix( $boardid );
1795
1796 // Get the old 2022 theme styles for this board
1797 $styles_2022 = get_option( $prefix . 'styles_2022', [] );
1798
1799 // If user had 2022 styles, copy them to 2026 theme
1800 if( ! empty( $styles_2022 ) && ! get_option( $prefix . 'styles_2026', [] ) ) {
1801 update_option( $prefix . 'styles_2026', $styles_2022 );
1802 }
1803 }
1804
1805 // Mark migration as complete
1806 update_option( 'wpforo_theme_styles_migrated', 1 );
1807 }
1808
1809 /**
1810 * Migrate AI quality settings to optimized defaults for better performance.
1811 * Changes quality tiers from 'advanced' to 'fast'/'balanced' to reduce API timeouts.
1812 * This only runs once during plugin update.
1813 *
1814 * New defaults (3.0.3+):
1815 * - search_quality: 'fast' (was 'balanced')
1816 * - search_enhance_quality: 'balanced' (was 'advanced')
1817 * - translation_quality: 'fast' (was 'advanced')
1818 * - topic_summary_quality: 'balanced' (was 'advanced')
1819 * - moderation_spam_quality: 'balanced' (was 'advanced')
1820 * - chatbot_quality: 'balanced' (was 'fast')
1821 *
1822 * @since 3.0.3
1823 */
1824 function wpforo_migrate_ai_quality_defaults() {
1825 // Only run this migration once
1826 if( get_option( 'wpforo_ai_quality_defaults_migrated' ) ) {
1827 return;
1828 }
1829
1830 // Define quality settings to update (old_value => new_value)
1831 $quality_changes = [
1832 'search_quality' => 'fast',
1833 'search_enhance_quality' => 'balanced',
1834 'translation_quality' => 'fast',
1835 'topic_summary_quality' => 'balanced',
1836 'moderation_spam_quality' => 'balanced',
1837 'chatbot_quality' => 'balanced',
1838 ];
1839
1840 // Get all board IDs (including default board 0)
1841 $boardids = [ 0 ];
1842 if( class_exists( 'wpforo\\classes\\Board' ) && method_exists( WPF()->board, 'get_active_boardids' ) ) {
1843 $boardids = array_unique( array_merge( $boardids, (array) WPF()->board->get_active_boardids() ) );
1844 }
1845
1846 foreach( $boardids as $boardid ) {
1847 // Build option name: wpforo_ai (board 0) or wpforo_{boardid}_ai (other boards)
1848 $option_name = 'wpforo_' . ( $boardid ? $boardid . '_' : '' ) . 'ai';
1849 $ai_settings = get_option( $option_name, [] );
1850
1851 // Skip if no settings saved (new install, will use new defaults)
1852 if( empty( $ai_settings ) ) {
1853 continue;
1854 }
1855
1856 // Update only the quality settings
1857 $updated = false;
1858 foreach( $quality_changes as $key => $new_value ) {
1859 // Only update if the key exists (user has this setting saved)
1860 if( isset( $ai_settings[ $key ] ) ) {
1861 $ai_settings[ $key ] = $new_value;
1862 $updated = true;
1863 }
1864 }
1865
1866 // Save updated settings
1867 if( $updated ) {
1868 update_option( $option_name, $ai_settings );
1869 }
1870 }
1871
1872 // Mark migration as complete
1873 update_option( 'wpforo_ai_quality_defaults_migrated', 1 );
1874 }
1875