| 开发者 | brokensmile.2103 |
|---|---|
| 更新时间 | 2026年10月25日 02:44 |
| PHP版本: | 7.4 及以上 |
| WordPress版本: | 7.1 |
| 版权: | GPLv2 or later |
| 版权网址: | 版权信息 |
/wp-content/plugins/init-user-engine[init_user_engine] in any page/post/template= How do I customize the UI? =\ The frontend is written in modular Vanilla JS with minimal HTML structure.\ Override styles via your theme or inject custom JS as needed.
user_meta: EXP, level, Coin, Cash, VIP, referralwp_init_user_engine_inbox: inbox messages (custom DB table)
= Can I extend or integrate it? =\
Yes. The plugin is built around WordPress hooks and REST API. You can inject logic via add_action, add_filter, or build your own UI on top of the endpoints.UPDATE … SET pinned = 0 WHERE pinned = 1 AND status = 'read' on the whole Inbox table (a one-time backfill for the "unpin when read" behaviour of 1.6.2). On large tables it showed up in the slow query log (about 2 seconds) even though there was nothing left to change. It now only runs when upgrading from a version older than 1.6.20). Read messages received more than the set number of days ago are deleted once a day in the background, so the Inbox table no longer grows forever on busy sitesALTER TABLE on every siteDELETE), so each statement only locks a small part of the table and members' Inboxes stay responsive. Each run stops after about 20 seconds and continues a minute later where it left off. In our test with 1.6 million messages, the first cleanup removed 1.26 million read messages in about 24 seconds over 2 runs, no DELETE took more than 0.31 seconds, and the next daily run (with nothing left to delete) took 0.3 secondscreated_at) are still found: the whole id range is scanned, not only the ids before the cutoff date0 removes the scheduled task at once. Deactivating the plugin removes it too (a cleanup in progress continues after reactivation); uninstalling deletes its optionsinit_plugin_suite_user_engine_inbox_auto_delete_days, init_plugin_suite_user_engine_inbox_auto_delete_batch, init_plugin_suite_user_engine_inbox_auto_delete_time_limit and init_plugin_suite_user_engine_inbox_auto_delete_keep_meta_keys (metadata keys that mark a message to keep, default created_by), action init_plugin_suite_user_engine_inbox_auto_deleted (number deleted, cutoff)init_plugin_suite_user_engine_dashboard_stats_max_age), a refresh is scheduled and the previous numbers are shown meanwhile, with "Updated X ago"extract() call from the widgetadministrator role instead of a capability; it now checks manage_optionshome_url( add_query_arg( [] ) ) to wp_login_url() instead of null to avoid compatibility issues with newer PHP versionsinit_plugin_suite_user_engine_log_transaction() / init_plugin_suite_user_engine_log_exp() keep their signature and return value, and the ..._transaction_logged / ..._exp_logged actions still fire right awaywp_cache_incr(). When more than 20,000 rows are waiting (filter init_plugin_suite_user_engine_write_behind_max_pending, about 4–5 MB), new rows go straight to the database. Rows expire from the cache after a daykill -9) in the middle of a batch, and with 50 processes writing at the same time while the flush ran: every row was written exactly onceinit_plugin_suite_user_engine_flush_interval). On sites that set DISABLE_WP_CRON, your system cron job runs it; that constant is never taken to mean the site has no cron. Visitors' and admins' requests never write the buffer, except when an admin clicks Write buffered logs now or deactivates the plugininit_plugin_suite_user_engine_write_behind_stall_minutes), a warning is showninit_plugin_suite_user_engine_write_behind_enabled, init_plugin_suite_user_engine_flush_interval, init_plugin_suite_user_engine_write_behind_max_pending, init_plugin_suite_user_engine_write_behind_chunk and init_plugin_suite_user_engine_write_behind_stall_minutes, action init_plugin_suite_user_engine_write_behind_flushed, and init_plugin_suite_user_engine_write_behind_bypass() to write directly from code that runs its own transactionusermeta table. The atomic UPDATE added in 1.6.8 picked the wallet row with a subquery on the same table (WHERE umeta_id = ( SELECT id FROM ( SELECT MIN(umeta_id) … ) )), which MySQL/MariaDB cannot resolve through the primary key: EXPLAIN showed a full index scan (type: index, about 800k rows). In our 800k-row benchmark each check-in, reward, purchase, exchange or top-up spent about 0.3 seconds in the database, and the cost grew with the tableuser_id index (only the rows of that one user are read), then changed by an UPDATE … WHERE umeta_id = … on the primary key: 1 row examined instead of the whole table, under 1 ms in the same benchmarkWHERE clause of the UPDATE. If the row changes between the lookup and the UPDATE, nothing is written and the change is retried. Tested with 40 parallel requests on one wallet: no balance spent twice, no credit lostUPDATE by primary key: no multi-table UPDATE … JOIN, deterministic for statement-based replication, and no new index on WordPress core tablesinit_plugin_suite_user_engine_wallet_read(), the X-IUE-Coin / X-IUE-Cash headers, GET /balance) no longer use ORDER BY umeta_id LIMIT 1, which can make MySQL walk the primary key instead of the user_id index. The oldest row is picked in PHP, so the result is the sameinit_plugin_suite_user_engine_coin_changed / ..._cash_changed actions are unchangedGET /referral-log) now compares iue_referred_by as a string, the way it is stored, instead of making the database convert every value to a number (which also matched values such as 12abc). The invited members are loaded with one query instead of one query per memberSQL_CALC_FOUND_ROWS)esc_like()-escaped helper instead of raw SHOW TABLES LIKE '…' queriesget_users() loaded every user ID inside the admin request, then made one wallet change, one log row and one Inbox message per member; with tens of thousands of members the request could hit the PHP time limit halfway, with no way to know who had been paidID > cursor, never all at once and never with OFFSET. Each slice runs for up to 20 seconds (or half of max_execution_time when lower), then the next slice is scheduled right awaykill -9) in the middle of a member: that member was rolled back and handled again, and all 12,003 test members were paid exactly onceDISABLE_WP_CRON. The admin page never runs a job by itself, so heavy work you moved to a system cron stays there. When PHP has no time limit (WP-CLI, or php wp-cron.php run from the command line by a system cron job), each run processes up to 60 seconds of workinit_plugin_suite_user_engine_bulk_job_stall_minutes; keep it longer than your system cron interval), its row shows a warning and a Process from this page button. Only after the admin clicks it does the open page process the job, a few seconds per request, sharing the same lock as WP-Croninit_plugin_suite_user_engine_admin_send_notice now fires once per batch, with the members of that batch. New actions init_plugin_suite_user_engine_bulk_job_created and init_plugin_suite_user_engine_bulk_job_finished, new filters init_plugin_suite_user_engine_bulk_job_time_budget, init_plugin_suite_user_engine_bulk_job_batch_size, init_plugin_suite_user_engine_bulk_job_query_args and init_plugin_suite_user_engine_bulk_job_stall_minutesiue_custom_avatar user meta, which cost one query per commenter not yet in the cache. The users and user meta of the whole list are now loaded at once with cache_users(): 40 comments from 40 different members went from 82 queries to 5. This covers the comment template, the Recent Comments block/widget, the wp-admin Comments screen and the REST APIwp-admin/profile.php (WordPress lets members change it freely there), the core REST API (/wp/v2/users/me), WooCommerce Account details, and any other plugin that calls wp_update_user() for the signed-in member. The check runs in wp_pre_insert_user_data, so a change made too early keeps the current display namewp-admin/profile.php and WooCommerce Account details show a clear message with the date and time of the next allowed change. While the member has to wait, the display name field is disabled in the modal and on profile.phpdisplay_name_locked, HTTP 403) and saves nothing when the display name cannot be changed yet; saving other fields with the same display name still worksiue_display_name_changed_at). Changes are recorded even while the limit is 0, so a limit set later applies right awayinit_plugin_suite_user_engine_display_name_change_days and init_plugin_suite_user_engine_display_name_limit_applies, new action init_plugin_suite_user_engine_display_name_unlocked, and new PHP helpers init_plugin_suite_user_engine_is_display_name_locked( $user_id ), init_plugin_suite_user_engine_get_display_name_next_change( $user_id ) and init_plugin_suite_user_engine_unlock_display_name( $user_id )GET /wp-json/inituser/v1/profile/me now includes display_name_limit (days, locked, next_change, notice)POST /profile/update now returns the display name actually saved") broke the display name field in the Edit Profile modal. The value is now set safely