PluginProbe
wpForo Forum / 3.2.0
wpForo Forum v3.2.0
3.2.0 3.1.7 3.1.6 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 All 140 releases
wpforo / includes / installation.php

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

1,883 lines 74.8 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
509 // gVectors News module cleanup — shared gvectors_* data is removed only when
510 // no other gVectors plugin (wpDiscuz, ...) carrying the module remains installed
511 if( class_exists( \gVectors\News\NewsModule::class ) ) {
512 $gv_news_config = \gVectors\News\NewsModule::get( WPFORO_BASEFOLDER, 'config' );
513 if( $gv_news_config ) \gVectors\News\NewsModule::uninstall( $gv_news_config );
514 }
515
516 deactivate_plugins( WPFORO_BASENAME );
517 }
518
519 function wpforo_profile_notice() {
520 if( is_multisite() ) {
521 $users = WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->db->usermeta . "` WHERE `meta_key` LIKE '" . WPF()->blog_prefix . "capabilities'" );
522 } else {
523 $users = WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->db->users . "`" );
524 }
525 $profiles = WPF()->db->get_var( "SELECT COUNT(*) FROM `" . WPF()->tables->profiles . "`" );
526 $delta = $users - $profiles;
527 $status = ( $delta > 2 ) ? round( ( ( $profiles * 100 ) / $users ), 1 ) . '% (' . $profiles . ' / ' . $users . ') ' : '100%';
528 if( $status === '100%' ) return null;
529 $btext = ( $profiles == 0 ) ? __( 'Start Profile Synchronization', 'wpforo' ) : __( 'Continue Synchronization', 'wpforo' );
530 $url = wp_nonce_url( admin_url( 'admin.php?page=wpforo-overview&wpfaction=synch_user_profiles' ), 'wpforo_synch_user_profiles' );
531 $class = 'wpforo-mnote notice notice-warning is-dismissible';
532 $note = __( 'This process may take a few seconds or dozens of minutes, please be patient and don\'t close this page.', 'wpforo' );
533 $info = __( 'You can permanently disable this message using this documentation', 'wpforo' );
534 $button = '<a href="' . $url . '" class="button button-primary button-large" style="font-size:14px;">' . $btext . ' &gt;&gt;</a>';
535 $header = __( 'wpForo Forum Installation | ', 'wpforo' );
536 $message = __( 'Forum users\' profile data are not synchronized yet, this step is required! Please click the button below to complete installation.', 'wpforo' );
537 echo '<div class="' . $class . '" style="padding:15px 20px;"><h2 style="margin:0;">' . esc_html(
538 $header
539 ) . $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>';
540 }
541
542 function wpforo_database_notice() {
543 $url = admin_url( 'admin.php?page=' . wpforo_prefix_slug( 'tools' ) . '&tab=tables' );
544 $class = 'wpforo-dbnote notice notice-error is-dismissible';
545 $button = '<a href="' . $url . '" class="button button-primary button-large" style="font-size:13px;">Go to Database Troubleshooter &gt;&gt;</a>';
546 $header = __( 'wpForo Database Update Problem - Action Required!', 'wpforo' );
547 $message = __( 'Forum database is not updated properly. Please click the button below for further instruction.', 'wpforo' );
548 echo '<div class="' . $class . '" style="padding:15px 20px;"><h2 style="margin:0;">' . esc_html(
549 $header
550 ) . ' </h2><p style="font-size:15px;margin:5px 0;">' . $message . '</p><p style="margin:15px 0 0 0;">' . $button . '</p></div>';
551 }
552
553 function wpforo_cache_information() {
554 $plugin_names = [];
555 $plugin_steps = [];
556 $not_excluded_plugins = WPF()->cache->cache_plugins_status();
557 if( ! empty( $not_excluded_plugins ) ) {
558 foreach( $not_excluded_plugins as $key => $plugin ) {
559 $plugin_names[ $key ] = '&laquo;' . $plugin['name'] . '&raquo;';
560 $plugin_steps[ $key ] = '<h4 style="margin: 0; font-size: 14px;">' . __( 'Exclude forum page(s) from' ) . ' ' . $plugin['name'] . ' ' . __(
561 'plugin',
562 'wpforo'
563 ) . '</h4><ol style="font-size: 14px; color: #6203a2;"><li style="margin-bottom: 4px;">' . implode( '</li><li style="margin-bottom: 4px;">', $plugin['steps'] ) . '</ol></li>';
564 }
565 }
566 $class = 'wpforo-cache-conflict-note notice notice-error is-dismissible';
567 $note = sprintf(
568 __( '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' ),
569 ( is_rtl() ? __( 'left', 'wpforo' ) : __( 'right', 'wpforo' ) )
570 );
571 $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>';
572 $header = 'wpForo ' . __( ' and ', 'wpforo' ) . implode( __( ' and ', 'wpforo' ), $plugin_names ) . ' ' . __( 'conflict', 'wpforo' ) . ' - ' . __( 'Action Required!', 'wpforo' );
573 $message = __(
574 '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.',
575 'wpforo'
576 ) . '<br />' . implode( '<br>', $plugin_steps );
577
578 echo '<div class="' . $class . '" style="padding:15px 20px;"><h2 style="margin:0;">' . esc_html(
579 $header
580 ) . ' </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>';
581 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>";
582 }
583
584 function wpforo_get_shortcode_pageid( $exclude = [] ) {
585 $exclude = array_filter( array_map( 'wpforo_bigintval', (array) $exclude ) );
586 $sql = "SELECT `ID` FROM `" . WPF()->db->posts . "`
587 WHERE `post_content` LIKE '%[wpforo]%'
588 AND `post_status` LIKE 'publish'
589 AND `post_type` IN('" . implode( "','", wpforo_get_blog_content_types() ) . "')";
590 if( $exclude ) $sql .= " AND `ID` NOT IN(" . implode( ',', $exclude ) . ")";
591
592 return WPF()->db->get_var( $sql );
593 }
594
595 function wpforo_create_forum_page() {
596 $pageid = WPF()->board->get_current( 'pageid' );
597 if( ! $pageid || ! WPF()->db->get_var(
598 "SELECT `ID` FROM `" . WPF(
599 )->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(
600 "','",
601 wpforo_get_blog_content_types()
602 ) . "')"
603 ) ) {
604 if( ! $page_id = wpforo_get_shortcode_pageid( get_option( 'page_on_front' ) ) ) {
605 $wpforo_page = [
606 'post_date' => current_time( 'mysql', 1 ),
607 'post_date_gmt' => current_time( 'mysql', 1 ),
608 'post_content' => '[wpforo]',
609 'post_title' => 'Forum',
610 'post_status' => 'publish',
611 'comment_status' => 'close',
612 'ping_status' => 'close',
613 'post_name' => 'community',
614 'post_modified' => current_time( 'mysql', 1 ),
615 'post_modified_gmt' => current_time( 'mysql', 1 ),
616 'post_parent' => 0,
617 'menu_order' => 0,
618 'post_type' => 'page',
619 ];
620 $page_id = wp_insert_post( $wpforo_page );
621 }
622 if( $page_id && ! is_wp_error( $page_id ) ) {
623 wpforo_update_option( 'wpforo_pageid', $page_id );
624 }
625 }
626
627 flush_rewrite_rules( false );
628 nocache_headers();
629 }
630
631 function wpforo_repair_main_shortcode_page() {
632 $pageid = WPF()->board->get_current( 'pageid' );
633 $sql = "SELECT `ID` FROM `" . WPF()->db->posts . "`
634 WHERE `ID` = " . $pageid . "
635 AND `post_content` LIKE '%[wpforo%'
636 AND `post_status` LIKE 'publish'
637 AND `post_type` IN('" . implode( "','", wpforo_get_blog_content_types() ) . "')";
638 if( ! $pageid || ! WPF()->db->get_var( $sql ) ) {
639 wpforo_create_forum_page();
640 } else {
641 flush_rewrite_rules( false );
642 nocache_headers();
643 }
644 }
645
646 function wpforo_import_default_board() {
647 if( ! WPF()->board->get_current( 'boardid' ) && ! WPF()->board->_get_board( 0 ) ) {
648 $blogname = get_option( 'blogname', '' );
649 $_general = wpforo_get_option( 'wpforo_general_options', [
650 'title' => $blogname . ' ' . __( 'Forum', 'wpforo' ),
651 'description' => $blogname . ' ' . __( 'Discussion Board', 'wpforo' ),
652 'lang' => 1,
653 ], false );
654 $board = WPF()->board->get_current();
655 $board['slug'] = basename( trim( (string) wpforo_get_option( 'wpforo_permastruct', 'community', false ), '/' ) );
656 $board['is_standalone'] = wpforo_get_option( 'wpforo_use_home_url', false );
657 $board['excld_urls'] = array_filter( array_map( 'trim', explode( PHP_EOL, (string) wpforo_get_option( 'wpforo_excld_urls', '' ) ) ) );
658 $board['settings'] = [];
659 $board['settings']['title'] = $_general['title'];
660 $board['settings']['desc'] = $_general['description'];
661 if( $boardid = WPF()->board->add( $board ) ) {
662 WPF()->board->edit( [ 'boardid' => 0 ], $boardid );
663 }
664 }
665 }
666
667 function wpforo_import_default_menus() {
668 $boardid = WPF()->board->get_current( 'boardid' );
669 $menu_name = wpforo_phrase( 'wpForo Navigation', false, 'orig' ) . ( $boardid ? ' #' . $boardid : '' );
670 $menu_location = wpforo_prefix_slug( 'menu' );
671 $menu_exists = wp_get_nav_menu_object( $menu_name );
672 if( ! $menu_exists ) {
673 $id = [];
674 $menu_id = wp_create_nav_menu( $menu_name );
675 $id['wpforo-home'] = wp_update_nav_menu_item( $menu_id, 0, [
676 'menu-item-title' => wpforo_phrase( 'Forums', false ),
677 'menu-item-classes' => 'wpforo-home',
678 'menu-item-url' => '/%wpforo-home%/',
679 'menu-item-status' => 'publish',
680 'menu-item-parent-id' => 0,
681 'menu-item-position' => 0,
682 ] );
683
684 $id['wpforo-recent-activity'] = wp_update_nav_menu_item( $menu_id, 0, [
685 'menu-item-title' => wpforo_phrase( 'What\'s New', false ),
686 'menu-item-classes' => 'wpforo-whats-new',
687 'menu-item-url' => '/%wpforo-recent-activity%/',
688 'menu-item-status' => 'publish',
689 'menu-item-parent-id' => 0,
690 'menu-item-position' => 0,
691 ] );
692
693 $id['wpforo-recent'] = wp_update_nav_menu_item( $menu_id, 0, [
694 'menu-item-title' => wpforo_phrase( 'Recent Posts', false ),
695 'menu-item-classes' => 'wpforo-recent',
696 'menu-item-url' => '/%wpforo-recent%/',
697 'menu-item-status' => 'publish',
698 'menu-item-parent-id' => 0,
699 'menu-item-position' => 0,
700 ] );
701
702 $id['wpforo-members'] = wp_update_nav_menu_item( $menu_id, 0, [
703 'menu-item-title' => wpforo_phrase( 'Members', false ),
704 'menu-item-classes' => 'wpforo-members',
705 'menu-item-url' => '/%wpforo-members%/',
706 'menu-item-status' => 'publish',
707 'menu-item-parent-id' => 0,
708 'menu-item-position' => 0,
709 ] );
710
711 $id['wpforo-profile'] = wp_update_nav_menu_item( $menu_id, 0, [
712 'menu-item-title' => wpforo_phrase( 'My Profile', false ),
713 'menu-item-classes' => 'wpforo-profile',
714 'menu-item-url' => '/%wpforo-profile-home%/',
715 'menu-item-status' => 'publish',
716 'menu-item-parent-id' => 0,
717 'menu-item-position' => 0,
718 ] );
719
720 if( isset( $id['wpforo-profile'] ) && $id['wpforo-profile'] ) {
721 $id['wpforo-profile-account'] = wp_update_nav_menu_item( $menu_id, 0, [
722 'menu-item-title' => wpforo_phrase( 'Account', false ),
723 'menu-item-classes' => 'wpforo-profile-account',
724 'menu-item-url' => '/%wpforo-profile-account%/',
725 'menu-item-status' => 'publish',
726 'menu-item-parent-id' => $id['wpforo-profile'],
727 'menu-item-position' => 1,
728 ] );
729 $id['wpforo-profile-activity'] = wp_update_nav_menu_item( $menu_id, 0, [
730 'menu-item-title' => wpforo_phrase( 'Activity', false ),
731 'menu-item-classes' => 'wpforo-profile-activity',
732 'menu-item-url' => '/%wpforo-profile-activity%/',
733 'menu-item-status' => 'publish',
734 'menu-item-parent-id' => $id['wpforo-profile'],
735 'menu-item-position' => 1,
736 ] );
737 $id['wpforo-profile-subscriptions'] = wp_update_nav_menu_item( $menu_id, 0, [
738 'menu-item-title' => wpforo_phrase( 'Subscriptions', false ),
739 'menu-item-classes' => 'wpforo-profile-subscriptions',
740 'menu-item-url' => '/%wpforo-profile-subscriptions%/',
741 'menu-item-status' => 'publish',
742 'menu-item-parent-id' => $id['wpforo-profile'],
743 'menu-item-position' => 2,
744 ] );
745 }
746
747 $id['wpforo-register'] = wp_update_nav_menu_item( $menu_id, 0, [
748 'menu-item-title' => wpforo_phrase( 'Register', false ),
749 'menu-item-classes' => 'wpforo-register',
750 'menu-item-url' => '/%wpforo-register%/',
751 'menu-item-status' => 'publish',
752 'menu-item-parent-id' => 0,
753 'menu-item-position' => 0,
754 ] );
755
756 $id['wpforo-login'] = wp_update_nav_menu_item( $menu_id, 0, [
757 'menu-item-title' => wpforo_phrase( 'Login', false ),
758 'menu-item-classes' => 'wpforo-login',
759 'menu-item-url' => '/%wpforo-login%/',
760 'menu-item-status' => 'publish',
761 'menu-item-parent-id' => 0,
762 'menu-item-position' => 0,
763 ] );
764
765 $id['wpforo-logout'] = wp_update_nav_menu_item( $menu_id, 0, [
766 'menu-item-title' => wpforo_phrase( 'Logout', false ),
767 'menu-item-classes' => 'wpforo-logout',
768 'menu-item-url' => '/%wpforo-logout%/',
769 'menu-item-status' => 'publish',
770 'menu-item-parent-id' => 0,
771 'menu-item-position' => 0,
772 ] );
773
774 if( ! has_nav_menu( $menu_location ) ) {
775 $locations = get_theme_mod( 'nav_menu_locations' );
776 if( empty( $locations ) ) $locations = [];
777 $locations[ $menu_location ] = $menu_id;
778 set_theme_mod( 'nav_menu_locations', $locations );
779 }
780 }
781 }
782
783 function wpforo_import_default_accesses() {
784 $cans_n = [
785 'vf' => 0,
786 'enf' => 0,
787 'ct' => 0,
788 'vt' => 0,
789 'ent' => 0,
790 'et' => 0,
791 'dt' => 0,
792 'cr' => 0,
793 'ocr' => 0,
794 'vr' => 0,
795 'er' => 0,
796 'dr' => 0,
797 'tag' => 0,
798 'eot' => 0,
799 'eor' => 0,
800 'dot' => 0,
801 'dor' => 0,
802 'sb' => 0,
803 'l' => 0,
804 'r' => 0,
805 's' => 0,
806 'au' => 0,
807 'p' => 0,
808 'op' => 0,
809 'vp' => 0,
810 'sv' => 0,
811 'osv' => 0,
812 'v' => 0,
813 'vop' => 0,
814 'vlp' => 0,
815 'a' => 0,
816 'va' => 0,
817 'at' => 0,
818 'oat' => 0,
819 'aot' => 0,
820 'cot' => 0,
821 'mt' => 0,
822 // Poll-related permissions moved to wpForo Polls plugin
823 ];
824 $cans_r = [
825 'vf' => 1,
826 'enf' => 1,
827 'ct' => 0,
828 'vt' => 1,
829 'ent' => 1,
830 'et' => 0,
831 'dt' => 0,
832 'cr' => 0,
833 'ocr' => 0,
834 'vr' => 1,
835 'er' => 0,
836 'dr' => 0,
837 'tag' => 0,
838 'eot' => 0,
839 'eor' => 0,
840 'dot' => 0,
841 'dor' => 0,
842 'sb' => 1,
843 'l' => 0,
844 'r' => 0,
845 's' => 0,
846 'au' => 0,
847 'p' => 0,
848 'op' => 0,
849 'vp' => 0,
850 'sv' => 0,
851 'osv' => 0,
852 'v' => 0,
853 'vop' => 0,
854 'vlp' => 1,
855 'a' => 0,
856 'va' => 1,
857 'at' => 0,
858 'oat' => 0,
859 'aot' => 0,
860 'cot' => 0,
861 'mt' => 0,
862 // Poll-related permissions moved to wpForo Polls plugin
863 ];
864 $cans_s = [
865 'vf' => 1,
866 'enf' => 1,
867 'ct' => 1,
868 'vt' => 1,
869 'ent' => 1,
870 'et' => 0,
871 'dt' => 0,
872 'cr' => 1,
873 'ocr' => 1,
874 'vr' => 1,
875 'er' => 0,
876 'dr' => 0,
877 'tag' => 1,
878 'eot' => 1,
879 'eor' => 1,
880 'dot' => 1,
881 'dor' => 1,
882 'sb' => 1,
883 'l' => 1,
884 'r' => 1,
885 's' => 0,
886 'au' => 0,
887 'p' => 0,
888 'op' => 1,
889 'vp' => 0,
890 'sv' => 0,
891 'osv' => 1,
892 'v' => 1,
893 'vop' => 1,
894 'vlp' => 1,
895 'a' => 1,
896 'va' => 1,
897 'at' => 0,
898 'oat' => 1,
899 'aot' => 1,
900 'cot' => 0,
901 'mt' => 0,
902 // Poll-related permissions moved to wpForo Polls plugin
903 ];
904 $cans_m = [
905 'vf' => 1,
906 'enf' => 1,
907 'ct' => 1,
908 'vt' => 1,
909 'ent' => 1,
910 'et' => 1,
911 'dt' => 1,
912 'cr' => 1,
913 'ocr' => 1,
914 'vr' => 1,
915 'er' => 1,
916 'dr' => 1,
917 'tag' => 1,
918 'eot' => 1,
919 'eor' => 1,
920 'dot' => 1,
921 'dor' => 1,
922 'sb' => 1,
923 'l' => 1,
924 'r' => 1,
925 's' => 1,
926 'au' => 1,
927 'p' => 1,
928 'op' => 1,
929 'vp' => 1,
930 'sv' => 1,
931 'osv' => 1,
932 'v' => 1,
933 'vop' => 1,
934 'vlp' => 1,
935 'a' => 1,
936 'va' => 1,
937 'at' => 1,
938 'oat' => 1,
939 'aot' => 1,
940 'cot' => 1,
941 'mt' => 1,
942 // Poll-related permissions moved to wpForo Polls plugin
943 ];
944 $cans_a = [
945 'vf' => 1,
946 'enf' => 1,
947 'ct' => 1,
948 'vt' => 1,
949 'ent' => 1,
950 'et' => 1,
951 'dt' => 1,
952 'cr' => 1,
953 'ocr' => 1,
954 'vr' => 1,
955 'er' => 1,
956 'dr' => 1,
957 'tag' => 1,
958 'eot' => 1,
959 'eor' => 1,
960 'dot' => 1,
961 'dor' => 1,
962 'sb' => 1,
963 'l' => 1,
964 'r' => 1,
965 's' => 1,
966 'au' => 1,
967 'p' => 1,
968 'op' => 1,
969 'vp' => 1,
970 'sv' => 1,
971 'osv' => 1,
972 'v' => 1,
973 'vop' => 1,
974 'vlp' => 1,
975 'a' => 1,
976 'va' => 1,
977 'at' => 1,
978 'oat' => 1,
979 'aot' => 1,
980 'cot' => 1,
981 'mt' => 1,
982 // Poll-related permissions moved to wpForo Polls plugin
983 ];
984
985 //Add new Accesses in this array to add those in custom Accesses created by forum admin
986 $cans_default = [
987 'sb' => 1,
988 'au' => 1,
989 'p' => 0,
990 'op' => 1,
991 'vp' => 0,
992 // Poll-related permissions moved to wpForo Polls plugin
993 'aot' => 1,
994 'tag' => 1,
995 'ocr' => 0,
996 'enf' => 1,
997 'ent' => 1,
998 'vop' => 1,
999 'vlp' => 1,
1000 ];
1001
1002 $sql = "SELECT * FROM `" . WPF()->tables->accesses . "`";
1003 $accesses = WPF()->db->get_results( $sql, ARRAY_A );
1004 if( empty( $accesses ) ) {
1005 $cans_n = serialize( $cans_n );
1006 $cans_r = serialize( $cans_r );
1007 $cans_s = serialize( $cans_s );
1008 $cans_m = serialize( $cans_m );
1009 $cans_a = serialize( $cans_a );
1010
1011 $sql = "INSERT IGNORE INTO `" . WPF()->tables->accesses . "`
1012 (`access`, `title`, cans) VALUES
1013 ('no_access', 'No access', '" . $cans_n . "'),
1014 ('read_only', 'Read only access', '" . $cans_r . "'),
1015 ('standard', 'Standard access', '" . $cans_s . "'),
1016 ('moderator', 'Moderator access', '" . $cans_m . "'),
1017 ('full', 'Full access', '" . $cans_a . "')";
1018
1019 WPF()->db->query( $sql );
1020 } else {
1021 foreach( $accesses as $access ) {
1022 $current = unserialize( $access['cans'] );
1023 if( strtolower( (string) $access['access'] ) === 'no_access' ) {
1024 $default = $cans_n;
1025 } elseif( strtolower( (string) $access['access'] ) === 'read_only' ) {
1026 $default = $cans_r;
1027 } elseif( strtolower( (string) $access['access'] ) === 'standard' ) {
1028 $default = $cans_s;
1029 } elseif( strtolower( (string) $access['access'] ) === 'moderator' ) {
1030 $default = $cans_m;
1031 } elseif( strtolower( (string) $access['access'] ) === 'full' ) {
1032 $default = $cans_a;
1033 } else {
1034 $default = $cans_default;
1035 }
1036 if( ! empty( $default ) ) {
1037 $data_update = array_merge( $default, $current );
1038 if( ! empty( $data_update ) ) {
1039 $data_update = serialize( $data_update );
1040 WPF()->db->query( "UPDATE `" . WPF()->tables->accesses . "` SET `cans` = '" . WPF()->db->_real_escape( $data_update ) . "' WHERE `accessid` = " . intval( $access['accessid'] ) );
1041 }
1042 }
1043 }
1044 }
1045 }
1046
1047 function wpforo_import_default_usergroups() {
1048 $cans_admin = [
1049 'mf' => 1,
1050 'mai' => 1,
1051 'ms' => 1,
1052 'mt' => 1,
1053 'mp' => 1,
1054 'mth' => 1,
1055 'vm' => 1,
1056 'aum' => 1,
1057 'em' => 1,
1058 'vmg' => 1,
1059 'aup' => 1,
1060 'vmem' => 1,
1061 'view_stat' => 1,
1062 'vprf' => 1,
1063 'vpra' => 1,
1064 'vprs' => 1,
1065 'bm' => 1,
1066 'dm' => 1,
1067 'upc' => 1,
1068 'upa' => 1,
1069 'ups' => 1,
1070 'va' => 1,
1071 'vmu' => 1,
1072 'vmm' => 1,
1073 'vmt' => 1,
1074 'vmct' => 1,
1075 'vmr' => 1,
1076 'vmw' => 1,
1077 'vmsn' => 1,
1078 'vmrd' => 1,
1079 'vml' => 1,
1080 'vmo' => 1,
1081 'vms' => 1,
1082 'vmam' => 1,
1083 'vwpm' => 1,
1084 'caa' => 1,
1085 'vt_add_topic' => 1,
1086 'ai_search' => 1,
1087 'ai_summary' => 1,
1088 'ai_translation' => 1,
1089 'ai_suggestion' => 1,
1090 ];
1091 $cans_moder = [
1092 'mf' => 0,
1093 'ms' => 0,
1094 'mai' => 0,
1095 'mt' => 0,
1096 'mp' => 0,
1097 'mth' => 0,
1098 'vm' => 0,
1099 'aum' => 1,
1100 'em' => 0,
1101 'vmg' => 0,
1102 'aup' => 1,
1103 'vmem' => 1,
1104 'view_stat' => 1,
1105 'vprf' => 1,
1106 'vpra' => 1,
1107 'vprs' => 1,
1108 'bm' => 1,
1109 'dm' => 1,
1110 'upc' => 1,
1111 'upa' => 1,
1112 'ups' => 1,
1113 'va' => 1,
1114 'vmu' => 0,
1115 'vmm' => 1,
1116 'vmt' => 1,
1117 'vmct' => 1,
1118 'vmr' => 1,
1119 'vmw' => 1,
1120 'vmsn' => 1,
1121 'vmrd' => 1,
1122 'vml' => 1,
1123 'vmo' => 1,
1124 'vms' => 1,
1125 'vmam' => 1,
1126 'vwpm' => 1,
1127 'caa' => 1,
1128 'vt_add_topic' => 1,
1129 'ai_search' => 1,
1130 'ai_summary' => 1,
1131 'ai_translation' => 1,
1132 'ai_suggestion' => 1,
1133 ];
1134 $cans_reg = [
1135 'mf' => 0,
1136 'ms' => 0,
1137 'mai' => 0,
1138 'mt' => 0,
1139 'mp' => 0,
1140 'mth' => 0,
1141 'vm' => 0,
1142 'aum' => 0,
1143 'em' => 0,
1144 'vmg' => 0,
1145 'aup' => 1,
1146 'vmem' => 1,
1147 'view_stat' => 1,
1148 'vprf' => 1,
1149 'vpra' => 1,
1150 'vprs' => 0,
1151 'bm' => 0,
1152 'dm' => 0,
1153 'upc' => 1,
1154 'upa' => 1,
1155 'ups' => 1,
1156 'va' => 1,
1157 'vmu' => 0,
1158 'vmm' => 0,
1159 'vmt' => 1,
1160 'vmct' => 1,
1161 'vmr' => 1,
1162 'vmw' => 1,
1163 'vmsn' => 1,
1164 'vmrd' => 1,
1165 'vml' => 1,
1166 'vmo' => 1,
1167 'vms' => 1,
1168 'vmam' => 1,
1169 'vwpm' => 1,
1170 'caa' => 1,
1171 'vt_add_topic' => 1,
1172 'ai_search' => 1,
1173 'ai_summary' => 1,
1174 'ai_translation' => 1,
1175 'ai_suggestion' => 1,
1176 ];
1177 $cans_guest = [
1178 'mf' => 0,
1179 'ms' => 0,
1180 'mai' => 0,
1181 'mt' => 0,
1182 'mp' => 0,
1183 'mth' => 0,
1184 'vm' => 0,
1185 'aum' => 0,
1186 'em' => 0,
1187 'vmg' => 0,
1188 'aup' => 0,
1189 'vmem' => 1,
1190 'view_stat' => 1,
1191 'vprf' => 1,
1192 'vpra' => 1,
1193 'vprs' => 0,
1194 'bm' => 0,
1195 'dm' => 0,
1196 'upc' => 0,
1197 'upa' => 0,
1198 'ups' => 0,
1199 'va' => 1,
1200 'vmu' => 0,
1201 'vmm' => 0,
1202 'vmt' => 1,
1203 'vmct' => 1,
1204 'vmr' => 1,
1205 'vmw' => 0,
1206 'vmsn' => 1,
1207 'vmrd' => 1,
1208 'vml' => 1,
1209 'vmo' => 1,
1210 'vms' => 1,
1211 'vmam' => 1,
1212 'vwpm' => 0,
1213 'caa' => 1,
1214 'vt_add_topic' => 0,
1215 'ai_search' => 1,
1216 'ai_summary' => 1,
1217 'ai_translation' => 1,
1218 'ai_suggestion' => 0,
1219 ];
1220 $cans_customer = [
1221 'mf' => 0,
1222 'ms' => 0,
1223 'mai' => 0,
1224 'mt' => 0,
1225 'mp' => 0,
1226 'mth' => 0,
1227 'vm' => 0,
1228 'aum' => 0,
1229 'em' => 0,
1230 'vmg' => 0,
1231 'aup' => 0,
1232 'vmem' => 1,
1233 'view_stat' => 1,
1234 'vprf' => 1,
1235 'vpra' => 1,
1236 'vprs' => 0,
1237 'bm' => 0,
1238 'dm' => 0,
1239 'upc' => 1,
1240 'upa' => 1,
1241 'ups' => 1,
1242 'va' => 1,
1243 'vmu' => 0,
1244 'vmm' => 0,
1245 'vmt' => 1,
1246 'vmct' => 1,
1247 'vmr' => 1,
1248 'vmw' => 1,
1249 'vmsn' => 1,
1250 'vmrd' => 1,
1251 'vml' => 1,
1252 'vmo' => 1,
1253 'vms' => 1,
1254 'vmam' => 1,
1255 'vwpm' => 1,
1256 'caa' => 1,
1257 'vt_add_topic' => 1,
1258 'ai_search' => 1,
1259 'ai_summary' => 1,
1260 'ai_translation' => 1,
1261 'ai_suggestion' => 1,
1262 ];
1263
1264 //Add new Cans in this array to add those in custom Usergroup created by forum admin
1265 $cans_defaults = [
1266 'mf' => 0,
1267 'ms' => 0,
1268 'mai' => 0,
1269 'mt' => 0,
1270 'mp' => 0,
1271 'mth' => 0,
1272 'vmem' => 1,
1273 'view_stat' => 1,
1274 'vprf' => 1,
1275 'caa' => 1,
1276 'vt_add_topic' => 1,
1277 'upc' => 1,
1278 'ai_search' => 1,
1279 'ai_summary' => 1,
1280 'ai_translation' => 1,
1281 'ai_suggestion' => 1,
1282 ];
1283
1284 $sql = "SELECT * FROM `" . WPF()->tables->usergroups . "`";
1285 if( ! $usergroups = WPF()->db->get_results( $sql, ARRAY_A ) ) {
1286 WPF()->usergroup->add( 'Admin', $cans_admin, '', 'administrator', 'full', '#FF3333' );
1287 WPF()->usergroup->add( 'Moderator', $cans_moder, '', 'editor', 'moderator', '#0066FF' );
1288 WPF()->usergroup->add( 'Registered', $cans_reg, '', 'subscriber', 'standard', '', 1, 1 );
1289 WPF()->usergroup->add( 'Guest', $cans_guest, '', '', 'read_only', '#222222', 0 );
1290 WPF()->usergroup->add( 'Customer', $cans_customer, '', 'customer', 'standard', '#993366', 1, 1 );
1291 } else {
1292 foreach( $usergroups as $usergroup ) {
1293 $current = unserialize( $usergroup['cans'] );
1294 if( strtolower( (string) $usergroup['name'] ) === 'admin' ) {
1295 $default = $cans_admin;
1296 } elseif( strtolower( (string) $usergroup['name'] ) === 'moderator' ) $default = $cans_moder;
1297 elseif( strtolower( (string) $usergroup['name'] ) === 'registered' ) $default = $cans_reg;
1298 elseif( strtolower( (string) $usergroup['name'] ) === 'guest' ) $default = $cans_guest;
1299 elseif( strtolower( (string) $usergroup['name'] ) === 'customer' ) $default = $cans_customer;
1300 else {
1301 $default = $cans_defaults;
1302 }
1303 if( ! empty( $default ) ) {
1304 $data_update = array_merge( $default, $current );
1305 if( ! empty( $data_update ) ) {
1306 $data_update = serialize( $data_update );
1307 WPF()->db->query(
1308 "UPDATE `" . WPF()->tables->usergroups . "` SET `cans` = '" . WPF()->db->_real_escape( $data_update ) . "' WHERE `groupid` = " . intval( $usergroup['groupid'] )
1309 );
1310 }
1311 }
1312 }
1313 }
1314 }
1315
1316 function wpforo_import_default_forums() {
1317 $sql = "SELECT COUNT(*) FROM `" . WPF()->tables->forums . "`";
1318 $count = WPF()->db->get_var( $sql );
1319 if( ! $count ) {
1320 if( $parentid = WPF()->forum->add(
1321 [ 'title' => __( 'Main Category', 'wpforo' ), 'description' => __( 'This is a simple category / section', 'wpforo' ), 'layout' => 4, 'icon' => 'fas fa-comments' ],
1322 false
1323 ) ) {
1324 WPF()->forum->add(
1325 [ 'title' => __( 'Main Forum', 'wpforo' ), 'description' => __( 'This is a simple parent forum', 'wpforo' ), 'parentid' => $parentid, 'layout' => 4, 'icon' => 'fas fa-comments' ],
1326 false
1327 );
1328 }
1329 }
1330 }
1331
1332 function wpforo_fix_wp_permalink_structure() {
1333 $permalink_structure = get_option( 'permalink_structure' );
1334 if( ! $permalink_structure ) {
1335 global $wp_rewrite;
1336 $wp_rewrite->set_permalink_structure( '/%postname%/' );
1337 }
1338 }
1339
1340 function wpforo_fix_installed_options() {
1341 /**
1342 * in the update process, using old features settings set the right profile setting for the new option
1343 */
1344 if( $features = wpforo_get_option( 'features', [], false ) ) {
1345 if( ! (int) wpfval( $features, 'profile' ) ) {
1346 if( (int) wpfval( $features, 'bp_profile' ) && class_exists( 'BP_Component' ) ) {
1347 $features['profile'] = 3;
1348 } elseif( (int) wpfval( $features, 'um_profile' ) && function_exists( 'UM' ) ) {
1349 $features['profile'] = 4;
1350 } elseif( (int) wpfval( $features, 'comment-author-link' ) ) {
1351 $features['profile'] = 2;
1352 } else {
1353 $features['profile'] = 1;
1354 }
1355 wpforo_update_option( 'features', array_map( 'intval', $features ) );
1356 }
1357 }
1358
1359 #################################################################
1360 // CHECK Addon Notice /////////////////////////////////////////
1361 $lastHash = get_option( 'wpforo_addon_note_dismissed' );
1362 $first = get_option( 'wpforo_addon_note_first' );
1363 if( $lastHash && $first === 'true' ) {
1364 update_option( 'wpforo_addon_note_first', 'false' );
1365 }
1366
1367 #################################################################
1368 // AVOID PLUGIN CONFLICTS ///////////////////////////////////////
1369 /* Autoptimize *************************************************/
1370 $autopt = get_option( 'autoptimize_js_exclude' );
1371 if( $autopt && strpos( (string) $autopt, 'wp-includes/js/tinymce' ) === false ) {
1372 $autopt = $autopt . ', wp-includes/js/tinymce';
1373 update_option( 'autoptimize_js_exclude', $autopt );
1374 if( class_exists( 'autoptimizeCache' ) && is_callable( [ 'autoptimizeCache', 'clearall' ] ) ) {
1375 autoptimizeCache::clearall();
1376 }
1377 }
1378
1379 #################################################################
1380 // Adding #wpforo to custom css /////////////////////////////////
1381 if( $style_options = wpforo_get_option( 'style_options', [], false ) ) {
1382 $custom_css = wpfval( $style_options, 'custom_css' );
1383 if( $custom_css && strpos( (string) $custom_css, '#wpforo #wpforo-wrap' ) === false ) {
1384 $style_options['custom_css'] = str_replace( '#wpforo-wrap', '#wpforo #wpforo-wrap', $style_options['custom_css'] );
1385 wpforo_update_option( 'style_options', $style_options );
1386 }
1387 }
1388 }
1389
1390 function wpforo_update_theme_options() {
1391 if( $current_theme = wpforo_get_option( 'theme_options_' . WPF()->tpl->theme, [], false ) ) {
1392 $theme = WPF()->tpl->find_theme( '2026' );
1393 if( wpfval( $theme, 'layouts' ) ) {
1394 $current_theme['layouts'] = $theme['layouts'];
1395 $theme = wpforo_deep_merge( $theme, $current_theme );
1396 wpforo_update_option( 'theme_options_' . WPF()->tpl->theme, $theme );
1397 }
1398 }
1399 }
1400
1401 function wpforo_clean_up() {
1402 WPF()->db->delete( WPF()->tables->topics, [ 'first_postid' => 0 ], [ '%d' ] );
1403 }
1404
1405 // wpforo database checker fixer tools
1406 function wpforo_update_db() {
1407 $problems = wpforo_database_check();
1408 if( ! empty( $problems ) ) {
1409 $SQL = wpforo_database_fixer( $problems );
1410 if( wpfval( $SQL, 'fields' ) ) {
1411 foreach( $SQL['fields'] as $query ) WPF()->db->query( $query );
1412 }
1413 if( wpfval( $SQL, 'keys' ) ) {
1414 foreach( $SQL['keys'] as $query ) WPF()->db->query( $query );
1415 }
1416 if( wpfval( $SQL, 'tables' ) ) {
1417 foreach( $SQL['tables'] as $query ) {
1418 if( false === WPF()->db->query( $query ) ) {
1419 WPF()->db->query( preg_replace( '#\)[\r\n\t\s]*ENGINE.*$#isu', ')', $query ) );
1420 }
1421 }
1422 }
1423 if( wpfval( $SQL, 'data' ) ) {
1424 foreach( $SQL['data'] as $query ) {
1425 WPF()->db->query( $query );
1426 }
1427 }
1428 }
1429 wpforo_update_option( 'version_db', WPFORO_VERSION );
1430 }
1431
1432 /**
1433 * @param array $args
1434 *
1435 * @return bool|string|null
1436 */
1437 function wpforo_db_check( $args = [] ) {
1438 $key = [ 'wpforo_db_check', $args ];
1439 if( WPF()->ram_cache->exists( $key ) ) return WPF()->ram_cache->get( $key );
1440
1441 global $wpdb;
1442
1443 $col = esc_sql( trim( (string) wpfval( $args, 'col' ) ) );
1444 $table = esc_sql( trim( (string) wpfval( $args, 'table' ) ) );
1445
1446 $result = null;
1447 switch( trim( (string) wpfval( $args, 'check' ) ) ) {
1448 case 'table_exists':
1449 $result = (bool) $wpdb->get_var( "SHOW TABLES LIKE '$table'" );
1450 break;
1451 case 'col_exists':
1452 $result = (bool) $wpdb->get_var( "SHOW COLUMNS FROM `$table` LIKE '$col'" );
1453 break;
1454 case 'key_exists':
1455 $result = (bool) $wpdb->get_var( "SHOW KEYS FROM `$table` WHERE `Key_name` = '$col'" );
1456 break;
1457 case 'default_value':
1458 $c = (array) $wpdb->get_row( "SHOW COLUMNS FROM `$table` LIKE '$col'", ARRAY_A );
1459 $result = wpfval( $c, 'Default' );
1460 break;
1461 case 'col_type':
1462 $c = (array) $wpdb->get_row( "SHOW COLUMNS FROM `$table` LIKE '$col'", ARRAY_A );
1463 $result = wpfval( $c, 'Type' );
1464 break;
1465 }
1466
1467 WPF()->ram_cache->set( $key, $result );
1468
1469 return $result;
1470 }
1471
1472 function wpforo_database_check() {
1473 $_tables = [];
1474 $_table_diffs = [];
1475 require_once( WPFORO_DIR . '/includes/install-sql.php' );
1476 $wpforo_sql = wpforo_get_install_sqls();
1477 if( ! empty( $wpforo_sql ) ) {
1478 foreach( $wpforo_sql as $sql ) {
1479 if( preg_match( '|EXISTS \`([^\(]+)\`\s*\((.+)(PRIMARY.+)\)\s*ENGINE|is', $sql, $table ) ) {
1480 if( wpfval( $table, 1 ) ) {
1481 if( wpfval( $table, 2 ) ) {
1482 if( preg_match_all( '|\`([^\`]+)\`|is', $table[2], $fields, PREG_SET_ORDER ) ) {
1483 foreach( $fields as $field ) {
1484 if( wpfval( $field, 1 ) ) $_tables[ $table[1] ]['fields'][] = $field[1];
1485 }
1486 }
1487 }
1488 if( wpfval( $table, 3 ) ) {
1489 if( preg_match( '|PRIMARY KEY \(\`([^\`]+)\`\)|is', $table[3], $primary_key ) ) {
1490 $_tables[ $table[1] ]['keys'][] = $primary_key[1];
1491 }
1492 if( preg_match_all( '|KEY \`([^\`]+)\`|is', $table[3], $keys, PREG_SET_ORDER ) ) {
1493 foreach( $keys as $key ) {
1494 if( wpfval( $key, 1 ) ) $_tables[ $table[1] ]['keys'][] = $key[1];
1495 }
1496 }
1497 }
1498 }
1499 }
1500 }
1501 if( ! empty( $_tables ) ) {
1502 foreach( $_tables as $_name => $_structure ) {
1503 $_table_fields = [];
1504 $_table_keys = [];
1505 $_table_exists = WPF()->db->get_var( "SHOW TABLES LIKE '" . esc_sql( $_name ) . "'" );
1506 if( $_table_exists ) {
1507 //Problems - Missing Field
1508 if( wpfval( $_structure, 'fields' ) ) {
1509 $_fields = WPF()->db->get_results( "SHOW FULL COLUMNS FROM " . esc_sql( $_name ), ARRAY_A );
1510 foreach( $_fields as $_field ) $_table_fields[] = $_field['Field'];
1511 $_count_orig = count( $_structure['fields'] );
1512 $_count_curr = count( $_table_fields );
1513 if( $_count_curr < $_count_orig ) {
1514 $diff = array_diff( $_structure['fields'], $_table_fields );
1515 if( ! empty( $diff ) ) $_table_diffs[ $_name ]['fields'][ $_name ] = $diff;
1516 }
1517 }
1518 //Problems - Missing Key
1519 if( wpfval( $_structure, 'keys' ) ) {
1520 $_keys = WPF()->db->get_results( "SHOW KEYS FROM " . esc_sql( $_name ), ARRAY_A );
1521 foreach( $_keys as $_key ) {
1522 if( strpos( (string) $_key['Key_name'], 'PRIMARY' ) !== false ) {
1523 $_table_keys[] = $_key['Column_name'];
1524 } else {
1525 $_table_keys[] = $_key['Key_name'];
1526 }
1527 }
1528 $_table_keys = array_unique( $_table_keys );
1529 $_table_keys = array_values( $_table_keys );
1530 $_count_orig = count( $_structure['keys'] );
1531 $_count_curr = count( $_table_keys );
1532 if( $_count_curr < $_count_orig ) {
1533 $diff_keys = array_diff( $_structure['keys'], $_table_keys );
1534 if( ! empty( $diff_keys ) ) $_table_diffs[ $_name ]['keys'][ $_name ] = $diff_keys;
1535 }
1536 }
1537 } else {
1538 //Problems - Missing Table
1539 $_table_diffs[ $_name ]['exists'] = 'no';
1540 }
1541 }
1542 }
1543 }
1544
1545 $first_board = WPF()->db->get_row( "SELECT `boardid` FROM `" . WPF()->tables->boards . "` WHERE `boardid` = 0", ARRAY_A );
1546 if( empty( $first_board ) ) {
1547 $_table_diffs['data']['default_board'] = 'no';
1548 }
1549
1550 return $_table_diffs;
1551 }
1552
1553 function wpforo_database_parse() {
1554 $_tables = [];
1555 $_table_diffs = [];
1556 require_once( WPFORO_DIR . '/includes/install-sql.php' );
1557 $wpforo_sql = wpforo_get_install_sqls();
1558 if( ! empty( $wpforo_sql ) ) {
1559 foreach( $wpforo_sql as $sql ) {
1560 if( preg_match( '|EXISTS \`([^\(]+)\`\s*\((.+)(PRIMARY.+)\)\s*ENGINE|is', $sql, $table ) ) {
1561 if( wpfval( $table, 1 ) ) {
1562 if( wpfval( $table, 2 ) ) {
1563 $_tables[ $table[1] ]['fields'] = array_map( 'trim', explode( ',', $table[2] ) );
1564 }
1565 if( wpfval( $table, 3 ) ) {
1566 $_tables[ $table[1] ]['keys'] = array_map( 'trim', explode( PHP_EOL, $table[3] ) );
1567 }
1568 }
1569 }
1570 }
1571 }
1572
1573 return $_tables;
1574 }
1575
1576 function wpforo_database_fixer( $problems ) {
1577 $SQL = [];
1578 if( ! empty( $problems ) ) {
1579 require_once( WPFORO_DIR . '/includes/install-sql.php' );
1580 $table_structure = wpforo_database_parse();
1581 if( ! empty( $table_structure ) ) {
1582 foreach( $problems as $table_name => $problem ) {
1583 if( wpfval( $problem, 'fields' ) ) {
1584 foreach( $problem['fields'] as $problem_fields ) {
1585 if( ! empty( $problem_fields ) ) {
1586 foreach( $problem_fields as $problem_field ) {
1587 if( wpfval( $table_structure, $table_name, 'fields' ) ) {
1588 foreach( $table_structure[ $table_name ]['fields'] as $field_sql ) {
1589 if( strpos( (string) $field_sql, '`' . $problem_field . '`' ) !== false ) {
1590 $SQL['fields'][] = 'ALTER TABLE `' . $table_name . '` ADD ' . $field_sql . ';';
1591 }
1592 }
1593 }
1594 }
1595 }
1596 }
1597 }
1598 if( wpfval( $problem, 'keys' ) ) {
1599 foreach( $problem['keys'] as $problem_keys ) {
1600 if( ! empty( $problem_keys ) ) {
1601 foreach( $problem_keys as $problem_key ) {
1602 if( wpfval( $table_structure, $table_name, 'keys' ) ) {
1603 foreach( $table_structure[ $table_name ]['keys'] as $key_sql ) {
1604 if( preg_match( '|KEY \`' . $problem_key . '\`|is', (string) $key_sql ) ) {
1605 $SQL['keys'][] = 'ALTER TABLE `' . $table_name . '` ADD ' . trim( (string) $key_sql, ',' ) . ';';
1606 }
1607 }
1608 }
1609 }
1610 }
1611 }
1612 }
1613 if( wpfval( $problem, 'exists' ) ) {
1614 $wpforo_sql = wpforo_get_install_sqls();
1615 if( wpfval( $wpforo_sql, $table_name ) ) {
1616 $SQL['tables'][] = preg_replace( '|\t+|', ' ', $wpforo_sql[ $table_name ] );
1617 }
1618 }
1619 }
1620 }
1621 if( wpfval( $problems, 'data' ) ) {
1622 if( wpfval( $problems, 'data', 'default_board' ) ) {
1623 // Missing default board (ID:0)
1624 $board = [];
1625 $blogname = get_option( 'blogname', '' );
1626 $_general = wpforo_get_option( 'wpforo_general_options', [
1627 'title' => $blogname . ' ' . __( 'Forum', 'wpforo' ),
1628 'description' => $blogname . ' ' . __( 'Discussion Board', 'wpforo' ),
1629 ], false );
1630 $board['title'] = ( wpfval( $_general, 'title' ) ) ?: 'Forum';
1631 $board['settings']['title'] = ( wpfval( $_general, 'title' ) ) ?: 'Forum';
1632 $board['settings']['desc'] = ( wpfval( $_general, 'description' ) ) ?: 'Discussion Board';
1633 $settings = wp_json_encode( $board['settings'] );
1634 WPF()->db->query( "UPDATE `" . WPF()->tables->boards . "` SET `slug` = CONCAT('community-', `boardid`) WHERE `slug` = 'community' AND `boardid` != 0" );
1635 $slug = basename( trim( (string) wpforo_get_option( 'wpforo_permastruct', 'community', false ), '/' ) );
1636 $board['slug'] = ( $slug ) ?: 'community';
1637 $board['locale'] = wpforo_get_site_default_locale();
1638 $board['pageid'] = wpforo_get_option( 'wpforo_pageid', 0 );
1639 $all_modules = array_map( '__return_true', wpforo_get_modules_info() );
1640 $all_addons = array_map( '__return_true', wpforo_get_addons_info() );
1641 $modules = array_merge( $all_modules, $all_addons );
1642 $board['modules'] = wp_json_encode( array_map( function( $a ) { return (bool) intval( $a ); }, $modules ) );
1643 $SQL['data']['pre_board'] = "SET sql_mode='NO_AUTO_VALUE_ON_ZERO';";
1644 $SQL['data']['default_board'] = "INSERT INTO `" . WPF()->tables->boards . "` (`boardid`, `title`, `slug`, `pageid`, `modules`, `locale`, `is_standalone`, `excld_urls`, `status`, `settings`)
1645 VALUES(0, '" . esc_sql( $board['title'] ) . "', '" . esc_sql( $board['slug'] ) . "', " . intval( $board['pageid'] ) . ",
1646 '" . esc_sql( $board['modules'] ) . "',
1647 '" . esc_sql( $board['locale'] ) . "', 0, '[]', 1, '" . esc_sql( $settings ) . "');";
1648 }
1649 //Other data checking here...
1650 }
1651 }
1652
1653 return $SQL;
1654 }
1655
1656 function wpforo_add_unique_key( $table, $primary_key, $unique_key_name = '', $unique_fields = '' ) {
1657
1658 $table = esc_sql( trim( (string) $table ) );
1659 $primary_key = esc_sql( trim( (string) $primary_key ) );
1660 $unique_fields = esc_sql( trim( (string) $unique_fields, ',' ) );
1661 $unique_fields_clean = preg_replace( '|\([^()]+\)|', '', (string) $unique_fields );
1662 $remove_rows = '';
1663 $sql = "SELECT GROUP_CONCAT(`$primary_key`) duplicated_row_ids,
1664 COUNT(*) duplication_count FROM
1665 `$table` GROUP BY $unique_fields_clean HAVING duplication_count > 1";
1666
1667 $rows = WPF()->db->get_results( $sql, ARRAY_A );
1668 if( ! empty( $rows ) ) {
1669 foreach( $rows as $row ) {
1670 $ids = explode( ',', $row['duplicated_row_ids'] );
1671 $ids = array_reverse( $ids );
1672 $ids = array_slice( $ids, 1 );
1673 $remove_rows .= trim( implode( ',', $ids ), ',' ) . ',';
1674 }
1675 $remove_rows = esc_sql( trim( $remove_rows, ',' ) );
1676 if( $remove_rows ) {
1677 WPF()->db->query( "DELETE FROM `$table` WHERE `$primary_key` IN($remove_rows)" );
1678 }
1679 }
1680 $sql = "ALTER TABLE `$table` ADD UNIQUE KEY `$unique_key_name`( $unique_fields )";
1681 WPF()->db->query( $sql );
1682 }
1683
1684 /**
1685 * Add What’s New menu item on plugin update (2.x to 3.x migration only)
1686 * Adds "What’s New" right after the first [Forums] menu item.
1687 * This only runs once during the 2.x to 3.x update.
1688 *
1689 * @since 3.0.0
1690 */
1691 function wpforo_migrate_recent_activity_menu() {
1692 // Only run this migration once (2.x to 3.x update)
1693 if( get_option( 'wpforo_recent_activity_menu_migrated' ) ) {
1694 return;
1695 }
1696
1697 // Get all boards to migrate menus for each
1698 $boards = [];
1699 if( isset( WPF()->board ) && method_exists( WPF()->board, 'get_boards' ) ) {
1700 $boards = WPF()->board->get_boards();
1701 }
1702
1703 // If no boards found or multi-board not initialized, just migrate default board
1704 if( empty( $boards ) ) {
1705 $boards = [ [ 'boardid' => 0 ] ];
1706 }
1707
1708 foreach( $boards as $board ) {
1709 $boardid = intval( wpfval( $board, 'boardid' ) );
1710 $menu_name = wpforo_phrase( 'wpForo Navigation', false, 'orig' ) . ( $boardid ? ' #' . $boardid : '' );
1711 $menu_obj = wp_get_nav_menu_object( $menu_name );
1712
1713 if( ! $menu_obj ) {
1714 continue; // Skip boards without a menu (fresh install will create menu with What's New)
1715 }
1716
1717 $menu_id = $menu_obj->term_id;
1718 $menu_items = wp_get_nav_menu_items( $menu_id );
1719
1720 if( empty( $menu_items ) ) {
1721 continue;
1722 }
1723
1724 // Check if "What's New" already exists (check both old and new CSS class names)
1725 $has_whats_new = false;
1726 foreach( $menu_items as $item ) {
1727 if( is_array( $item->classes ) && ( in_array( 'wpforo-whats-new', $item->classes ) || in_array( 'wpforo-recent-activity', $item->classes ) ) ) {
1728 $has_whats_new = true;
1729 break;
1730 }
1731 }
1732
1733 if( $has_whats_new ) {
1734 continue; // What's New already exists for this board, skip
1735 }
1736
1737 // Find the first [Forums] menu item (wpforo-home) to get its position
1738 $forums_position = 1;
1739 foreach( $menu_items as $item ) {
1740 if( is_array( $item->classes ) && in_array( 'wpforo-home', $item->classes ) ) {
1741 $forums_position = $item->menu_order;
1742 break;
1743 }
1744 }
1745
1746 // Shift all menu items after [Forums] by 1 position
1747 foreach( $menu_items as $item ) {
1748 if( $item->menu_order > $forums_position ) {
1749 wp_update_nav_menu_item( $menu_id, $item->db_id, [
1750 'menu-item-title' => $item->title,
1751 'menu-item-classes' => implode( ' ', (array) $item->classes ),
1752 'menu-item-url' => $item->url,
1753 'menu-item-status' => 'publish',
1754 'menu-item-parent-id' => $item->menu_item_parent,
1755 'menu-item-position' => $item->menu_order + 1,
1756 ] );
1757 }
1758 }
1759
1760 // Add "What's New" right after [Forums]
1761 wp_update_nav_menu_item( $menu_id, 0, [
1762 'menu-item-title' => wpforo_phrase( 'What\'s New', false ),
1763 'menu-item-classes' => 'wpforo-whats-new',
1764 'menu-item-url' => '/%wpforo-recent-activity%/',
1765 'menu-item-status' => 'publish',
1766 'menu-item-parent-id' => 0,
1767 'menu-item-position' => $forums_position + 1,
1768 ] );
1769 }
1770
1771 // Mark migration as complete - won't run again on future updates
1772 update_option( 'wpforo_recent_activity_menu_migrated', 1 );
1773 }
1774
1775 /**
1776 * Migrate theme styles from 2022 to 2026 theme on plugin update (2.x to 3.x migration)
1777 * Preserves user's custom colors and CSS when the default theme changes from 2022 to 2026.
1778 * Supports multi-board installations.
1779 * This only runs once during the 2.x to 3.x update.
1780 *
1781 * @since 3.0.0
1782 */
1783 function wpforo_migrate_theme_styles() {
1784 // Only run this migration once
1785 if( get_option( 'wpforo_theme_styles_migrated' ) ) {
1786 return;
1787 }
1788
1789 // Get all boards to migrate styles for each
1790 $boards = [];
1791 if( isset( WPF()->board ) && method_exists( WPF()->board, 'get_boards' ) ) {
1792 $boards = WPF()->board->get_boards();
1793 }
1794
1795 // If no boards found or multi-board not initialized, just migrate default board
1796 if( empty( $boards ) ) {
1797 $boards = [ [ 'boardid' => 0 ] ];
1798 }
1799
1800 foreach( $boards as $board ) {
1801 $boardid = intval( wpfval( $board, 'boardid' ) );
1802 $prefix = WPF()->generate_prefix( $boardid );
1803
1804 // Get the old 2022 theme styles for this board
1805 $styles_2022 = get_option( $prefix . 'styles_2022', [] );
1806
1807 // If user had 2022 styles, copy them to 2026 theme
1808 if( ! empty( $styles_2022 ) && ! get_option( $prefix . 'styles_2026', [] ) ) {
1809 update_option( $prefix . 'styles_2026', $styles_2022 );
1810 }
1811 }
1812
1813 // Mark migration as complete
1814 update_option( 'wpforo_theme_styles_migrated', 1 );
1815 }
1816
1817 /**
1818 * Migrate AI quality settings to optimized defaults for better performance.
1819 * Changes quality tiers from 'advanced' to 'fast'/'balanced' to reduce API timeouts.
1820 * This only runs once during plugin update.
1821 *
1822 * New defaults (3.0.3+):
1823 * - search_quality: 'fast' (was 'balanced')
1824 * - search_enhance_quality: 'balanced' (was 'advanced')
1825 * - translation_quality: 'fast' (was 'advanced')
1826 * - topic_summary_quality: 'balanced' (was 'advanced')
1827 * - moderation_spam_quality: 'balanced' (was 'advanced')
1828 * - chatbot_quality: 'balanced' (was 'fast')
1829 *
1830 * @since 3.0.3
1831 */
1832 function wpforo_migrate_ai_quality_defaults() {
1833 // Only run this migration once
1834 if( get_option( 'wpforo_ai_quality_defaults_migrated' ) ) {
1835 return;
1836 }
1837
1838 // Define quality settings to update (old_value => new_value)
1839 $quality_changes = [
1840 'search_quality' => 'fast',
1841 'search_enhance_quality' => 'balanced',
1842 'translation_quality' => 'fast',
1843 'topic_summary_quality' => 'balanced',
1844 'moderation_spam_quality' => 'balanced',
1845 'chatbot_quality' => 'balanced',
1846 ];
1847
1848 // Get all board IDs (including default board 0)
1849 $boardids = [ 0 ];
1850 if( class_exists( 'wpforo\\classes\\Board' ) && method_exists( WPF()->board, 'get_active_boardids' ) ) {
1851 $boardids = array_unique( array_merge( $boardids, (array) WPF()->board->get_active_boardids() ) );
1852 }
1853
1854 foreach( $boardids as $boardid ) {
1855 // Build option name: wpforo_ai (board 0) or wpforo_{boardid}_ai (other boards)
1856 $option_name = 'wpforo_' . ( $boardid ? $boardid . '_' : '' ) . 'ai';
1857 $ai_settings = get_option( $option_name, [] );
1858
1859 // Skip if no settings saved (new install, will use new defaults)
1860 if( empty( $ai_settings ) ) {
1861 continue;
1862 }
1863
1864 // Update only the quality settings
1865 $updated = false;
1866 foreach( $quality_changes as $key => $new_value ) {
1867 // Only update if the key exists (user has this setting saved)
1868 if( isset( $ai_settings[ $key ] ) ) {
1869 $ai_settings[ $key ] = $new_value;
1870 $updated = true;
1871 }
1872 }
1873
1874 // Save updated settings
1875 if( $updated ) {
1876 update_option( $option_name, $ai_settings );
1877 }
1878 }
1879
1880 // Mark migration as complete
1881 update_option( 'wpforo_ai_quality_defaults_migrated', 1 );
1882 }
1883