__( 'Activity log', 'frontglut' ), 'icon' => 'backup', 'group' => 'account', 'order' => 95, 'cap' => 'frontglut_manage_settings', 'render' => array( $this, 'render_audit' ) ); return $i; } ); } /* ---------------- REST ---------------- */ public function rest() { register_rest_route( 'frontglut/v1', '/me/context', array( 'methods' => 'GET', 'permission_callback' => 'is_user_logged_in', 'callback' => function () { $u = wp_get_current_user(); $modules = array(); foreach ( \FrontGlut\Core\Plugin::instance()->modules()->definitions() as $slug => $d ) { $modules[ $slug ] = frontglut_module_enabled( $slug ); } $caps = array(); foreach ( array_keys( \FrontGlut\Core\Caps::all() ) as $c ) { $caps[ $c ] = \FrontGlut\Core\Caps::can( $c ); } return array( 'id' => $u->ID, 'name' => $u->display_name, 'roles' => $u->roles, 'caps' => $caps, 'modules' => $modules, 'dashboard' => \FrontGlut\Modules\Dashboard\Dashboard::url() ); } ) ); register_rest_route( 'frontglut/v1', '/menu', array( 'methods' => 'GET', 'permission_callback' => 'is_user_logged_in', 'callback' => function () { $out = array(); foreach ( \FrontGlut\Modules\Dashboard\Dashboard::menu_items() as $id => $it ) { if ( \FrontGlut\Modules\Dashboard\Dashboard::item_allowed( $it ) && empty( $it['hidden'] ) ) { $out[] = array( 'id' => $id, 'label' => $it['label'], 'url' => ! empty( $it['url'] ) ? $it['url'] : \FrontGlut\Modules\Dashboard\Dashboard::url( $id ), 'group' => $it['group'] ); } } return $out; } ) ); register_rest_route( 'frontglut/v1', '/ping', array( 'methods' => 'GET', 'permission_callback' => '__return_true', 'callback' => function () { return array( 'ok' => true, 'version' => FRONTGLUT_VERSION ); } ) ); } /* ---------------- tabs ---------------- */ public function tabs( $t ) { $t['status'] = array( 'label' => __( 'System status', 'frontglut' ), 'order' => 90, 'cb' => array( $this, 'tab_status' ) ); $t['audit'] = array( 'label' => __( 'Activity log', 'frontglut' ), 'order' => 91, 'cb' => function () { echo wp_kses( $this->audit_table( 50 ), Ui::allowed_html() ); } ); $t['tools'] = array( 'label' => __( 'Tools', 'frontglut' ), 'order' => 92, 'cb' => array( $this, 'tab_tools' ) ); return $t; } /* ---------------- status ---------------- */ public function checks() { global $wpdb, $wp_version; $c = array(); $ok = function ( $label, $val, $good, $hint = '' ) use ( &$c ) { $c[] = array( $label, $val, $good, $hint ); }; $ok( 'FrontGlut', FRONTGLUT_VERSION, true ); $ok( 'WordPress', $wp_version, version_compare( $wp_version, '6.0', '>=' ) ); $ok( 'PHP', PHP_VERSION, version_compare( PHP_VERSION, '7.4', '>=' ), 'PHP 7.4+ required' ); $ok( 'MySQL', $wpdb->db_version(), true ); $mem = wp_convert_hr_to_bytes( WP_MEMORY_LIMIT ); // Handles shorthand such as 256M or 1G; -1 means unlimited. $ok( 'Memory limit', WP_MEMORY_LIMIT, -1 === $mem || $mem >= 128 * MB_IN_BYTES, '128M or more recommended' ); $ok( 'Max upload', size_format( wp_max_upload_size() ), true ); $ok( 'HTTPS', is_ssl() ? 'yes' : 'no', is_ssl(), 'Use HTTPS in production' ); $ok( 'WooCommerce', class_exists( 'WooCommerce' ) ? WC()->version : 'not active', true, 'Only needed for store modules' ); $ok( 'ZipArchive (ZIP downloads)', class_exists( 'ZipArchive' ) ? 'yes' : 'no', class_exists( 'ZipArchive' ) ); $ok( 'GD / Imagick', ( extension_loaded( 'gd' ) || extension_loaded( 'imagick' ) ) ? 'yes' : 'no', extension_loaded( 'gd' ) || extension_loaded( 'imagick' ) ); $ok( 'WP-Cron', ( defined( 'DISABLE_WP_CRON' ) && DISABLE_WP_CRON ) ? 'disabled (use system cron)' : 'enabled', true ); $ok( 'Daily job scheduled', wp_next_scheduled( 'frontglut_daily' ) ? gmdate( 'Y-m-d H:i', wp_next_scheduled( 'frontglut_daily' ) ) . ' UTC' : 'no', (bool) wp_next_scheduled( 'frontglut_daily' ) ); $missing = array(); foreach ( array( 'forms', 'audit', 'notifications', 'messages', 'tickets', 'ticket_replies', 'commissions', 'withdrawals', 'follows' ) as $t ) { if ( $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $wpdb->prefix . 'frontglut_' . $t ) ) !== $wpdb->prefix . 'frontglut_' . $t ) { $missing[] = $t; } } $ok( 'Database tables', $missing ? 'missing: ' . implode( ', ', $missing ) : 'all present', ! $missing ); $verify = apply_filters( 'https_local_ssl_verify', false ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core filter for loopback requests. $r = wp_remote_get( rest_url( 'frontglut/v1/ping' ), array( 'timeout' => 5, 'sslverify' => $verify ) ); $ok( 'REST API', ( ! is_wp_error( $r ) && 200 === wp_remote_retrieve_response_code( $r ) ) ? 'reachable' : 'unreachable', ! is_wp_error( $r ) && 200 === wp_remote_retrieve_response_code( $r ) ); foreach ( array( 'dashboard_page', 'login_page', 'register_page', 'lost_page', 'account_page' ) as $p ) { $id = (int) Settings::get( 'general.' . $p ); $ok( 'Page: ' . $p, $id && 'publish' === get_post_status( $id ) ? get_the_title( $id ) : 'missing', $id && 'publish' === get_post_status( $id ) ); } $up = wp_upload_dir(); $ok( 'Uploads writable', wp_is_writable( $up['basedir'] ) ? 'yes' : 'no', wp_is_writable( $up['basedir'] ) ); $ok( 'Active modules', implode( ', ', array_keys( array_filter( array_map( function ( $s ) { return frontglut_module_enabled( $s ); }, array_combine( array_keys( \FrontGlut\Core\Plugin::instance()->modules()->definitions() ), array_keys( \FrontGlut\Core\Plugin::instance()->modules()->definitions() ) ) ) ) ) ), true ); return apply_filters( 'frontglut_system_checks', $c ); } public function tab_status() { $rows = $this->checks(); echo ''; $report = array(); foreach ( $rows as $r ) { $report[] = $r[0] . ': ' . $r[1]; echo ''; } echo '
' . esc_html( $r[0] ) . '' . esc_html( $r[1] ) . '' . ( $r[2] ? '✓' : '✗ ' . esc_html( $r[3] ) ) . '

'; } /* ---------------- audit ---------------- */ public function audit_table( $per = 25 ) { global $wpdb; $pg = Request::page(); $ev = Request::filter( 'event', 'key', '' ); $where = $ev ? $wpdb->prepare( 'WHERE event = %s', $ev ) : ''; $tbl = $wpdb->prefix . 'frontglut_audit'; $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $tbl $where" ); // phpcs:ignore WordPress.DB.PreparedSQL $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $tbl $where ORDER BY id DESC LIMIT %d OFFSET %d", $per, ( $pg - 1 ) * $per ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL $events = $wpdb->get_col( "SELECT DISTINCT event FROM $tbl ORDER BY event" ); // phpcs:ignore WordPress.DB.PreparedSQL $out = Ui::filters( array( 'event' => array( __( 'All events', 'frontglut' ), array_combine( $events, $events ) ) ), array( 'event' => $ev ), false ); $data = array(); foreach ( $rows as $r ) { $u = $r['user_id'] ? get_userdata( $r['user_id'] ) : null; $data[] = array( 'when' => esc_html( get_date_from_gmt( $r['created'], 'Y-m-d H:i' ) ), 'user' => esc_html( $u ? $u->user_login : '—' ), 'event' => '' . esc_html( $r['event'] ) . '', 'obj' => esc_html( $r['object'] ), 'ip' => esc_html( $r['ip'] ) ); } $out .= Ui::table( array( 'when' => __( 'When', 'frontglut' ), 'user' => __( 'User', 'frontglut' ), 'event' => __( 'Event', 'frontglut' ), 'obj' => __( 'Object', 'frontglut' ), 'ip' => 'IP' ), $data ); $base = is_admin() ? array( 'page' => 'frontglut', 'tab' => 'audit', 'event' => $ev ) : array( 'event' => $ev ); return $out . Ui::pager( $total, $per, $pg, $base ); } public function render_audit() { return Ui::header( __( 'Activity log', 'frontglut' ) ) . $this->audit_table( 25 ); } /* ---------------- tools ---------------- */ public function tab_tools() { $u = function ( $do ) { return wp_nonce_url( admin_url( 'admin-post.php?action=frontglut_tool&do=' . $do ), 'frontglut_tool_' . $do ); }; echo '

' . esc_html__( 'Settings backup', 'frontglut' ) . '

' . esc_html__( 'Export settings (JSON)', 'frontglut' ) . '

'; echo '
'; wp_nonce_field( 'frontglut_tool_import' ); echo ''; submit_button( __( 'Import settings', 'frontglut' ), 'secondary' ); echo '

' . esc_html__( 'Maintenance', 'frontglut' ) . '

' . esc_html__( 'Clear caches (reports, rate limits)', 'frontglut' ) . ' ' . esc_html__( 'Flush rewrite rules', 'frontglut' ) . ' ' . esc_html__( 'Prune audit log now', 'frontglut' ) . '

'; echo '

' . esc_html__( 'Uninstall', 'frontglut' ) . '

' . ( get_option( 'frontglut_delete_on_uninstall' ) ? esc_html__( '✓ All data will be deleted when the plugin is deleted (click to turn off)', 'frontglut' ) : esc_html__( 'Keep data on uninstall (click to delete everything on uninstall)', 'frontglut' ) ) . '

'; echo '

' . esc_html__( 'Danger zone', 'frontglut' ) . '

' . esc_html__( 'Reset settings to defaults', 'frontglut' ) . '

'; } /** * Imported settings: every value is already text-sanitised; settings that have a field in the * settings screens are sanitised again by their field type, exactly as when saved from the form. */ private static function clean_import( array $in ) { $raw = $in; foreach ( Admin::schema() as $tab ) { foreach ( $tab['fields'] as $path => $f ) { $node = $raw; foreach ( explode( '.', $path ) as $part ) { $node = is_array( $node ) && array_key_exists( $part, $node ) ? $node[ $part ] : null; } if ( null === $node ) { continue; } $f['key'] = $path; $ref = &$in; foreach ( explode( '.', $path ) as $part ) { if ( ! isset( $ref[ $part ] ) || ! is_array( $ref[ $part ] ) ) { $ref[ $part ] = array(); } $ref = &$ref[ $part ]; } $ref = \FrontGlut\Support\Fields::sanitize( $f, $node ); unset( $ref ); } } return $in; } public function tool() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( 'Forbidden', 403 ); } $do = isset( $_REQUEST['do'] ) ? sanitize_key( $_REQUEST['do'] ) : ''; check_admin_referer( 'frontglut_tool_' . $do ); global $wpdb; $msg = __( 'Done.', 'frontglut' ); switch ( $do ) { case 'export': nocache_headers(); header( 'Content-Type: application/json' ); header( 'Content-Disposition: attachment; filename="frontglut-settings.json"' ); $s = get_option( Settings::OPTION, array() ); if ( isset( $s['captcha']['secret_key'] ) ) { $s['captcha']['secret_key'] = ''; } foreach ( array( 'google_secret', 'facebook_secret' ) as $k ) { if ( isset( $s['auth'][ $k ] ) ) { $s['auth'][ $k ] = ''; } } echo wp_json_encode( array( 'frontglut_settings' => $s, 'modules' => get_option( 'frontglut_modules', array() ) ), JSON_PRETTY_PRINT ); exit; case 'import': $d = \FrontGlut\Support\Sanitize::post_json( 'json' ); // Decoded and sanitised recursively. if ( isset( $d['frontglut_settings'] ) && is_array( $d['frontglut_settings'] ) ) { Settings::save( self::clean_import( $d['frontglut_settings'] ) ); if ( isset( $d['modules'] ) && is_array( $d['modules'] ) ) { $mods = array(); foreach ( $d['modules'] as $mk => $mv ) { $mods[ sanitize_key( $mk ) ] = (bool) $mv; } update_option( 'frontglut_modules', $mods, false ); } $msg = __( 'Settings imported.', 'frontglut' ); } else { $msg = __( 'Invalid file.', 'frontglut' ); } break; case 'cache': $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '\_transient\_frontglut\_%' OR option_name LIKE '\_transient\_timeout\_frontglut\_%'" ); // phpcs:ignore WordPress.DB.PreparedSQL break; case 'flush': flush_rewrite_rules(); break; case 'prune': Audit::prune(); break; case 'wipe_toggle': get_option( 'frontglut_delete_on_uninstall' ) ? delete_option( 'frontglut_delete_on_uninstall' ) : update_option( 'frontglut_delete_on_uninstall', 1, false ); break; case 'reset': delete_option( Settings::OPTION ); Settings::flush(); $msg = __( 'Settings reset.', 'frontglut' ); break; } Audit::log( 'tool_' . $do ); wp_safe_redirect( Admin::url( 'tools', Admin::flash( $msg ) ) ); exit; } /* ---------------- GDPR ---------------- */ public function exporters( $e ) { $e['frontglut'] = array( 'exporter_friendly_name' => 'FrontGlut', 'callback' => array( $this, 'export_user' ) ); return $e; } public function erasers( $e ) { $e['frontglut'] = array( 'eraser_friendly_name' => 'FrontGlut', 'callback' => array( $this, 'erase_user' ) ); return $e; } public function export_user( $email ) { global $wpdb; $u = get_user_by( 'email', $email ); $items = array(); if ( $u ) { foreach ( array( 'notifications' => 'user_id', 'messages' => 'from_id', 'tickets' => 'user_id', 'audit' => 'user_id' ) as $t => $col ) { $rows = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}frontglut_{$t} WHERE {$col} = %d LIMIT 500", $u->ID ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL foreach ( $rows as $r ) { $data = array(); foreach ( $r as $k => $v ) { $data[] = array( 'name' => $k, 'value' => is_scalar( $v ) ? (string) $v : '' ); } $items[] = array( 'group_id' => 'frontglut-' . $t, 'group_label' => 'FrontGlut: ' . $t, 'item_id' => $t . '-' . $r['id'], 'data' => $data ); } } } return array( 'data' => $items, 'done' => true ); } public function erase_user( $email ) { global $wpdb; $u = get_user_by( 'email', $email ); $n = 0; if ( $u ) { foreach ( array( 'notifications' => 'user_id', 'messages' => 'from_id', 'tickets' => 'user_id', 'audit' => 'user_id', 'follows' => 'user_id' ) as $t => $col ) { $n += (int) $wpdb->delete( $wpdb->prefix . 'frontglut_' . $t, array( $col => $u->ID ) ); } $wpdb->delete( $wpdb->prefix . 'frontglut_messages', array( 'to_id' => $u->ID ) ); delete_user_meta( $u->ID, 'frontglut_avatar' ); } return array( 'items_removed' => $n > 0, 'items_retained' => false, 'messages' => array(), 'done' => true ); } }