self::MENU_SLUG ); if ( '' !== $tab ) { $args['tab'] = $tab; } return add_query_arg( $args, admin_url( 'admin.php' ) ); } /** * Link to the settings in the list of plugins. * * @param string[] $links Links. * @return string[] */ public function action_links( $links ) { array_unshift( $links, '' . esc_html__( 'Open', 'vefdocskit' ) . '' ); return $links; } /** * Adds the menu entry. * * @return void */ public function menu() { $this->hook = (string) add_menu_page( __( 'VEFdocsKit', 'vefdocskit' ), __( 'VEFdocsKit', 'vefdocskit' ), VEFDK_Capabilities::MANAGE, self::MENU_SLUG, array( $this, 'render' ), 'dashicons-media-document', 58 ); } /** * The tabs the current user can open. * * Whoever cannot use the page does not see the work tabs. * * @return array> */ private function tabs() { $tabs = array(); if ( current_user_can( VEFDK_Accounts::CAP_USE ) ) { $tabs['work'] = array( 'label' => __( 'Documents', 'vefdocskit' ), 'icon' => 'dashicons-media-default', 'app' => 'process', 'description' => __( 'Turn images into PDF files and prepare your documents.', 'vefdocskit' ), 'help' => __( 'Drop your documents. Images become PDF files, and each PDF comes back rewritten cleanly, ready to download. The journal keeps the trace of everything.', 'vefdocskit' ), ); $tabs['tools'] = array( 'label' => __( 'Tools', 'vefdocskit' ), 'icon' => 'dashicons-admin-tools', 'app' => 'tools', 'description' => __( 'Work on the pages of your documents.', 'vefdocskit' ), 'help' => __( 'Merge, split, keep, delete, reorder and turn pages, number the pages, add a watermark, clean the metadata. Each result is kept and listed in the journal like any other document.', 'vefdocskit' ), ); $tabs['journal'] = array( 'label' => __( 'Journal', 'vefdocskit' ), 'icon' => 'dashicons-list-view', 'app' => 'journal', 'description' => __( 'The trace of every document handled.', 'vefdocskit' ), 'help' => __( 'Every document handled: who, when, with which tool, and the file to download while it is kept.', 'vefdocskit' ), ); } $tabs['accounts'] = array( 'label' => __( 'Accounts', 'vefdocskit' ), 'icon' => 'dashicons-groups', 'description' => __( 'The people who use the page of the documents.', 'vefdocskit' ), 'help' => __( 'Create the accounts of the people who use the page. They see that page and nothing else of the site: never this administration.', 'vefdocskit' ), ); $tabs['style'] = array( 'label' => __( 'Style', 'vefdocskit' ), 'icon' => 'dashicons-admin-appearance', 'description' => __( 'Adjust the look of the page of the documents.', 'vefdocskit' ), 'help' => __( 'The page takes the look of your site. Set a value here only to change it for the page of the documents; leave it empty to follow the site again. Each block, module or widget can still set its own accent, text color, corner radius and width.', 'vefdocskit' ), ); $tabs['settings'] = array( 'label' => __( 'Settings', 'vefdocskit' ), 'icon' => 'dashicons-admin-generic', 'description' => __( 'Limits and retention of the files.', 'vefdocskit' ), 'help' => __( 'The size of the documents, how long the files are kept, what happens when the plugin is deleted, and the export, import and reset of your configuration.', 'vefdocskit' ), ); $tabs['help'] = array( 'label' => __( 'Help', 'vefdocskit' ), 'icon' => 'dashicons-welcome-learn-more', 'description' => __( 'How to use VEFdocsKit.', 'vefdocskit' ), 'help' => __( 'The guide and information about the plugin.', 'vefdocskit' ), ); $tabs['support'] = array( 'label' => __( 'Diagnostic and support', 'vefdocskit' ), 'icon' => 'dashicons-sos', 'description' => __( 'Check your server and get help.', 'vefdocskit' ), 'help' => __( 'Checks of your server, and the way to ask for help.', 'vefdocskit' ), ); /** * Lets an extension add tabs. A tab is array( label, icon, description, help ) and either 'app' (a screen * of the page of the users, shown in the administration) or 'callback' (prints the tab). * * @param array> $tabs Tabs. */ $tabs = (array) apply_filters( 'vefdk_admin_tabs', $tabs ); foreach ( $tabs as $slug => $tab ) { $tabs[ $slug ]['url'] = self::url( $slug ); } return $tabs; } /** * The tab being shown. * * @param array> $tabs Available tabs. * @return string */ private function current_tab( array $tabs ) { $tab = isset( $_GET['tab'] ) ? sanitize_key( wp_unslash( $_GET['tab'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- choosing a screen, nothing is changed. return isset( $tabs[ $tab ] ) ? $tab : (string) array_key_first( $tabs ); } /** * Scripts and styles, on the screens of the plugin only. * * @param string $hook_suffix Screen. * @return void */ public function enqueue( $hook_suffix ) { if ( $this->hook !== $hook_suffix ) { return; } $tabs = $this->tabs(); $tab = $this->current_tab( $tabs ); wp_enqueue_style( 'vefdk-admin', VEFDK_PLUGIN_URL . 'assets/css/admin.css', array(), VEFDK_VERSION ); wp_enqueue_script( 'vefdk-admin-subpanels', VEFDK_PLUGIN_URL . 'assets/js/admin-subpanels.js', array(), VEFDK_VERSION, true ); if ( ! empty( $tabs[ $tab ]['app'] ) ) { VEFDK_Front::enqueue( 'admin' ); } if ( 'style' === $tab ) { VEFDK_Front::enqueue_style( 'admin' ); wp_enqueue_script( 'vefdk-admin-style', VEFDK_PLUGIN_URL . 'assets/js/admin-style.js', array(), VEFDK_VERSION, true ); } } /** * Contextual help of the screen (the "Help" button at the top right of the page). * * @param WP_Screen $screen Screen. * @return void */ public function help_tabs( $screen ) { if ( $this->hook !== $screen->id ) { return; } $tabs = $this->tabs(); $tab = $this->current_tab( $tabs ); if ( ! empty( $tabs[ $tab ]['help'] ) ) { $screen->add_help_tab( array( 'id' => 'vefdk-help-' . $tab, 'title' => $tabs[ $tab ]['label'], 'content' => '

' . esc_html( $tabs[ $tab ]['help'] ) . '

', ) ); } $screen->set_help_sidebar( '

' . esc_html__( 'VEFdocsKit', 'vefdocskit' ) . '

' . esc_html__( 'Open the guide', 'vefdocskit' ) . '

' ); } /** * Prints the page. * * @return void */ public function render() { if ( ! VEFDK_Capabilities::can_manage() ) { wp_die( esc_html__( 'You do not have permission to open this page.', 'vefdocskit' ), '', array( 'response' => 403 ) ); } $tabs = $this->tabs(); $tab = $this->current_tab( $tabs ); VEFDK_Admin_UI::render_page_top( $tabs[ $tab ]['label'], (string) ( $tabs[ $tab ]['description'] ?? '' ), $tabs[ $tab ]['icon'], $tabs, $tab ); $this->render_flash(); /** * Lets an extension print notices under the header of the page (a license to renew, an update…). */ do_action( 'vefdk_admin_notices' ); if ( ! empty( $tabs[ $tab ]['app'] ) ) { echo VEFDK_Front::render( 'admin', $tabs[ $tab ]['app'] ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaped where built. echo ''; return; } if ( ! empty( $tabs[ $tab ]['callback'] ) && is_callable( $tabs[ $tab ]['callback'] ) ) { call_user_func( $tabs[ $tab ]['callback'] ); echo ''; return; } switch ( $tab ) { case 'accounts': $this->render_accounts(); break; case 'style': $this->render_style(); break; case 'settings': $this->render_settings(); break; case 'help': VEFDK_Admin_UI::render_subpanels( 'help', array( 'guide' => array( 'label' => __( 'Guide', 'vefdocskit' ), 'callback' => array( 'VEFDK_Help', 'render_tab' ), ), 'about' => array( 'label' => __( 'About', 'vefdocskit' ), 'callback' => array( 'VEFDK_About', 'render_tab' ), ), ) ); break; case 'support': VEFDK_Admin_UI::render_subpanels( 'support', array( 'diagnostic' => array( 'label' => __( 'Diagnostic', 'vefdocskit' ), 'callback' => array( 'VEFDK_Diagnostic', 'render_tab' ), ), 'support' => array( 'label' => __( 'Support', 'vefdocskit' ), 'callback' => array( 'VEFDK_Support', 'render_tab' ), ), ) ); break; } echo ''; } /* ------------------------------------------------------------------------------------------ * Messages * ---------------------------------------------------------------------------------------- */ /** * Stores a message for the next screen. The result of an action is shown once after the redirect. * * @param string $kind success or error. * @param string $message Text. * @return void */ public static function flash( $kind, $message ) { set_transient( 'vefdk_flash_' . get_current_user_id(), array( $kind, $message ), 120 ); } /** * Prints the message stored by the last action. * * @return void */ private function render_flash() { $flash = get_transient( 'vefdk_flash_' . get_current_user_id() ); if ( ! is_array( $flash ) ) { return; } delete_transient( 'vefdk_flash_' . get_current_user_id() ); printf( '

%2$s

', 'error' === $flash[0] ? 'error' : 'success', esc_html( $flash[1] ) ); } /** * Common checks of a form: capability and nonce. Stops the request when one fails. * * @param string $action Nonce action. * @return void */ private function guard( $action ) { if ( ! current_user_can( 'manage_options' ) || VEFDK_Capabilities::is_external_helper() ) { wp_die( esc_html__( 'You do not have permission to do this.', 'vefdocskit' ), '', array( 'response' => 403 ) ); } check_admin_referer( $action ); } /** * Goes back to a tab. * * @param string $tab Tab. * @return void */ private function back( $tab ) { wp_safe_redirect( self::url( $tab ) ); exit; } /* ------------------------------------------------------------------------------------------ * Settings * ---------------------------------------------------------------------------------------- */ /** * The settings form. * * @return void */ private function render_settings() { $s = VEFDK_Settings::all(); $page = $s['page_id'] ? get_post( $s['page_id'] ) : null; ?>

post_status ) { printf( /* translators: %s: link to the page */ esc_html__( 'The plugin created this page for you: %s. You can rename it, move it in your menu, or put the block "VEFdocsKit" on another page.', 'vefdocskit' ), '' . esc_html( get_the_title( $page ) ) . '' ); } else { esc_html_e( 'The page does not exist any more. Create it again with the button below.', 'vefdocskit' ); } ?>

$s Settings. */ do_action( 'vefdk_settings_form', $s ); ?>

guard( 'vefdk_save_settings' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in guard(). VEFDK_Settings::update( array( 'max_mb' => isset( $_POST['max_mb'] ) ? absint( wp_unslash( $_POST['max_mb'] ) ) : 25, 'max_pages' => isset( $_POST['max_pages'] ) ? absint( wp_unslash( $_POST['max_pages'] ) ) : 400, 'retention_days' => isset( $_POST['retention_days'] ) ? absint( wp_unslash( $_POST['retention_days'] ) ) : 30, 'purge_on_logout' => ! empty( $_POST['purge_on_logout'] ) ? 1 : 0, 'delete_data' => ! empty( $_POST['delete_data'] ) ? 1 : 0, 'admin_sees_all' => ! empty( $_POST['admin_sees_all'] ) ? 1 : 0, ) ); // phpcs:enable WordPress.Security.NonceVerification.Missing /** * The settings were saved by an administrator whose nonce was checked: an extension saves its own fields. */ do_action( 'vefdk_save_settings_extra' ); $this->flash( 'success', __( 'Settings saved.', 'vefdocskit' ) ); $this->back( 'settings' ); } /** * Creates the page again when it was deleted. * * @return void */ public function recreate_page() { $this->guard( 'vefdk_recreate_page' ); $id = VEFDK_Installer::ensure_front_page(); $this->flash( $id ? 'success' : 'error', $id ? __( 'The page is ready.', 'vefdocskit' ) : __( 'The page could not be created.', 'vefdocskit' ) ); $this->back( 'settings' ); } /* ------------------------------------------------------------------------------------------ * Accounts * ---------------------------------------------------------------------------------------- */ /** * The accounts tab. * * @return void */ private function render_accounts() { $users = VEFDK_Accounts::front_users(); $can = VEFDK_Capabilities::can_manage_account(); ?>

' . esc_html__( 'the page of the documents', 'vefdocskit' ) . '' ); ?>

ID ); ?>
display_name ); ?> user_email ); ?> ! ✓ user_button( $user->ID, $suspended ? 'resume' : 'suspend', $suspended ? __( 'Reactivate', 'vefdocskit' ) : __( 'Suspend', 'vefdocskit' ) ); ?> user_button( $user->ID, 'reset', __( 'Send a new password link', 'vefdocskit' ) ); ?>

guard( 'vefdk_create_user' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in guard(). $id = VEFDK_Accounts::create( isset( $_POST['email'] ) ? sanitize_email( wp_unslash( $_POST['email'] ) ) : '', isset( $_POST['name'] ) ? sanitize_text_field( wp_unslash( $_POST['name'] ) ) : '' ); // phpcs:enable WordPress.Security.NonceVerification.Missing if ( is_wp_error( $id ) ) { $this->flash( 'error', $id->get_error_message() ); } else { $this->flash( 'success', __( 'Account created. The person received a link to choose a password.', 'vefdocskit' ) ); } $this->back( 'accounts' ); } /** * Suspends, reactivates an account, or sends a new password link. * * @return void */ public function user_action() { $this->guard( 'vefdk_user_action' ); // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified in guard(). $user_id = isset( $_POST['user_id'] ) ? absint( wp_unslash( $_POST['user_id'] ) ) : 0; $do = isset( $_POST['do'] ) ? sanitize_key( wp_unslash( $_POST['do'] ) ) : ''; // phpcs:enable WordPress.Security.NonceVerification.Missing $user = $user_id ? get_userdata( $user_id ) : false; // Only the accounts of the page: this screen never touches an administrator or an editor. if ( ! $user || ! in_array( VEFDK_Accounts::ROLE, (array) $user->roles, true ) ) { $this->flash( 'error', __( 'This account is not one of the page.', 'vefdocskit' ) ); $this->back( 'accounts' ); } if ( 'suspend' === $do ) { VEFDK_Accounts::set_suspended( $user_id, true ); $this->flash( 'success', __( 'Account suspended.', 'vefdocskit' ) ); } elseif ( 'resume' === $do ) { VEFDK_Accounts::set_suspended( $user_id, false ); $this->flash( 'success', __( 'Account reactivated.', 'vefdocskit' ) ); } elseif ( 'reset' === $do ) { $sent = retrieve_password( $user->user_login ); $this->flash( true === $sent ? 'success' : 'error', true === $sent ? __( 'A link to choose a new password was sent.', 'vefdocskit' ) : __( 'The message could not be sent.', 'vefdocskit' ) ); } $this->back( 'accounts' ); } /* ------------------------------------------------------------------------------------------ * Page style * ---------------------------------------------------------------------------------------- */ /** * The fields of the style, with the variable each one sets. * * @return array> */ private function style_fields() { return array( 'accent' => array( 'label' => __( 'Accent color', 'vefdocskit' ), 'type' => 'color', 'var' => '--vefdk-accent', 'help' => __( 'Buttons, links and the active tab. Empty: the link color of your theme.', 'vefdocskit' ), ), 'accent_ink' => array( 'label' => __( 'Text on the accent color', 'vefdocskit' ), 'type' => 'color', 'var' => '--vefdk-accent-ink', 'help' => __( 'Empty: black or white, whichever reads best.', 'vefdocskit' ), ), 'text' => array( 'label' => __( 'Text color', 'vefdocskit' ), 'type' => 'color', 'var' => '--vefdk-text', 'help' => __( 'Empty: the text color of your theme.', 'vefdocskit' ), ), 'background' => array( 'label' => __( 'Background', 'vefdocskit' ), 'type' => 'color', 'var' => '--vefdk-bg', 'help' => __( 'Empty: transparent, so the page keeps the background of your site.', 'vefdocskit' ), ), 'surface' => array( 'label' => __( 'Cards', 'vefdocskit' ), 'type' => 'color', 'var' => '--vefdk-surface', 'help' => __( 'Background of the cards. Empty: a light tint of the text color.', 'vefdocskit' ), ), 'border' => array( 'label' => __( 'Borders', 'vefdocskit' ), 'type' => 'color', 'var' => '--vefdk-border', 'help' => __( 'Empty: a light tint of the text color.', 'vefdocskit' ), ), 'radius' => array( 'label' => __( 'Corner radius', 'vefdocskit' ), 'type' => 'text', 'var' => '--vefdk-radius', 'help' => __( 'For example 4px. Empty: the radius of the fields of your theme.', 'vefdocskit' ), ), 'font' => array( 'label' => __( 'Font', 'vefdocskit' ), 'type' => 'text', 'var' => '--vefdk-font', 'help' => __( 'For example Georgia, serif. Empty: the font of your theme.', 'vefdocskit' ), ), 'width' => array( 'label' => __( 'Largest width', 'vefdocskit' ), 'type' => 'text', 'var' => '--vefdk-width', 'help' => __( 'For example 72rem. Empty: the width of the area where the page is placed.', 'vefdocskit' ), ), ); } /** * The style tab: the fields and a live sample. * * @return void */ private function render_style() { $style = VEFDK_Settings::get( 'style' ); $fields = $this->style_fields(); ?>

$field ) : ?>

guard( 'vefdk_save_style' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- verified in guard(); every value is cleaned by VEFDK_Settings::clean(). $raw = isset( $_POST['style'] ) && is_array( $_POST['style'] ) ? wp_unslash( $_POST['style'] ) : array(); VEFDK_Settings::update( array( 'style' => array_map( 'sanitize_text_field', $raw ) ) ); $this->flash( 'success', __( 'Style saved.', 'vefdocskit' ) ); $this->back( 'style' ); } }