requests = new DOCSINO_Admin_Requests(); $this->templates = new DOCSINO_Admin_Templates(); $this->settings = new DOCSINO_Admin_Settings(); $this->setup = new DOCSINO_Admin_Setup(); $this->pro_page = new DOCSINO_Admin_Pro_Page(); add_action( 'admin_menu', array( $this, 'register_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) ); add_filter( 'admin_body_class', array( $this, 'body_class' ) ); add_action( 'in_admin_header', array( $this, 'app_bar' ), 1000 ); add_filter( 'plugin_action_links_' . plugin_basename( DOCSINO_PLUGIN_FILE ), array( $this, 'action_links' ) ); add_filter( 'plugin_row_meta', array( $this, 'row_meta' ), 10, 2 ); add_action( 'admin_post_docsino_review_prompt', array( $this, 'handle_review_prompt' ) ); add_action( 'admin_post_docsino_toggle_wp_menu', array( $this, 'handle_toggle_wp_menu' ) ); add_action( 'admin_post_docsino_dismiss_exit_hint', array( $this, 'handle_dismiss_exit_hint' ) ); add_action( 'admin_notices', array( $this, 'start_notice_capture' ), PHP_INT_MIN ); add_action( 'all_admin_notices', array( $this, 'end_notice_capture' ), PHP_INT_MAX ); } /** * Whether the current user has chosen to see the WordPress menu on DocsInOrder screens. * Managers never do: WordPress has nothing else for them. * * @return bool */ public static function show_wp_menu() { return ! DOCSINO_Roles::is_manager_only() && (bool) get_user_meta( get_current_user_id(), 'docsino_show_wp_menu', true ); } /** * Start collecting notices from WordPress and other plugins on DocsInOrder screens. */ public function start_notice_capture() { if ( self::is_own_screen() && ! self::is_freemius_screen() ) { ob_start(); } } /** * Discard the collected notices. DocsInOrder screens stay clean; the * notices still appear on every other WordPress screen. DocsInOrder's * own messages are printed inside its pages, so they aren't affected. */ public function end_notice_capture() { if ( self::is_own_screen() && ! self::is_freemius_screen() ) { ob_end_clean(); } } /** * Turn the WordPress menu on or off on DocsInOrder screens (per user). */ public function handle_toggle_wp_menu() { self::authorize( 'docsino_toggle_wp_menu' ); update_user_meta( get_current_user_id(), 'docsino_show_wp_menu', self::show_wp_menu() ? 0 : 1 ); self::redirect( wp_get_referer() ? wp_get_referer() : self::page_url() ); } /** * Hide the one-time "back to WordPress" hint. */ public function handle_dismiss_exit_hint() { self::authorize( 'docsino_dismiss_exit_hint' ); update_user_meta( get_current_user_id(), 'docsino_seen_exit_hint', 1 ); self::redirect( wp_get_referer() ? wp_get_referer() : self::page_url() ); } /** * Add the top-level menu and its submenus. */ public function register_menu() { $cap = docsino_capability(); add_menu_page( __( 'DocsInOrder', 'docs-in-order' ), __( 'DocsInOrder', 'docs-in-order' ), $cap, 'docs-in-order', array( $this->requests, 'render_page' ), self::menu_icon(), 26 ); add_submenu_page( 'docs-in-order', __( 'Document requests', 'docs-in-order' ), __( 'Requests', 'docs-in-order' ), $cap, 'docs-in-order', array( $this->requests, 'render_page' ) ); add_submenu_page( 'docs-in-order', __( 'Templates', 'docs-in-order' ), __( 'Templates', 'docs-in-order' ), $cap, 'docs-in-order-templates', array( $this->templates, 'render_page' ) ); add_submenu_page( 'docs-in-order', __( 'Settings', 'docs-in-order' ), __( 'Settings', 'docs-in-order' ), 'manage_options', 'docs-in-order-settings', array( $this->settings, 'render_page' ) ); add_submenu_page( 'docs-in-order', __( 'Set up DocsInOrder', 'docs-in-order' ), __( 'Setup', 'docs-in-order' ), 'manage_options', 'docs-in-order-setup', array( $this->setup, 'render_page' ) ); add_submenu_page( 'docs-in-order', __( 'DocsInOrder Pro', 'docs-in-order' ), 'active' === DOCSINO_Admin_Pro_Page::state() ? __( 'Pro', 'docs-in-order' ) : ( 'inactive' === DOCSINO_Admin_Pro_Page::state() ? __( 'Activate Pro', 'docs-in-order' ) : __( 'Get Pro', 'docs-in-order' ) ), 'manage_options', DOCSINO_Admin_Pro_Page::PAGE, array( $this->pro_page, 'render_page' ) ); } /** * Whether the current admin screen is one of DocsInOrder's. * * @return bool */ public static function is_own_screen() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading the current page only. $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; return 0 === strpos( $page, 'docs-in-order' ); } /** * Whether the current screen is one of Freemius's pages under our menu * (Account, Add-ons). Their licence messages must stay visible there. * * @return bool */ public static function is_freemius_screen() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading the current page only. $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; return in_array( $page, array( 'docs-in-order-pro', 'docs-in-order-account', 'docs-in-order-addons', 'docs-in-order-pricing', 'docs-in-order-contact', 'docs-in-order-affiliation', 'docs-in-order-wp-support-forum' ), true ); } /** * URL of the in-plugin Add-Ons page (DocsInOrder Pro), if Freemius is available. * * @return string */ public static function addons_url() { return self::page_url( DOCSINO_Admin_Pro_Page::PAGE ); } /** * A small, inline "Pro can do this" hint, shown only when Pro's feature isn't active. * * @param string $feature Feature key. * @param string $text Hint text. */ public static function pro_hint( $feature, $text ) { if ( docsino_has_feature( $feature ) || ! current_user_can( 'manage_options' ) ) { return; } printf( '
%1$s %2$s %4$s
', esc_html__( 'Pro', 'docs-in-order' ), esc_html( $text ), esc_url( self::addons_url() ), 'inactive' === DOCSINO_Admin_Pro_Page::state() ? esc_html__( 'Activate your licence', 'docs-in-order' ) : esc_html__( 'Learn more', 'docs-in-order' ) ); } /** * Load admin CSS and JS on DocsInOrder screens only. * * @param string $hook Current admin page hook. */ public function enqueue( $hook ) { if ( false === strpos( (string) $hook, 'docs-in-order' ) ) { return; } $deps = array(); if ( false !== strpos( (string) $hook, 'docs-in-order-settings' ) || false !== strpos( (string) $hook, 'docs-in-order-setup' ) ) { // The logo picker uses the Media Library; load it before our script. wp_enqueue_media(); $deps[] = 'media-editor'; } wp_enqueue_style( 'docsino-admin', DOCSINO_PLUGIN_URL . 'assets/css/admin.css', array(), docsino_asset_version( 'assets/css/admin.css' ) ); wp_enqueue_script( 'docsino-admin', DOCSINO_PLUGIN_URL . 'assets/js/admin.js', $deps, docsino_asset_version( 'assets/js/admin.js' ), true ); } /** * Mark DocsInOrder screens for the full-page layout. * * @param string $classes Body classes. * @return string */ public function body_class( $classes ) { if ( ! self::is_own_screen() ) { return $classes; } return $classes . ' dio-app' . ( self::show_wp_menu() ? ' dio-app--wp-menu' : '' ); } /** * Print the DocsInOrder app bar. */ public function app_bar() { if ( ! self::is_own_screen() ) { return; } $manager = DOCSINO_Roles::is_manager_only(); $show_menu = self::show_wp_menu(); $show_hint = ! $manager && ! get_user_meta( get_current_user_id(), 'docsino_seen_exit_hint', true ); // phpcs:disable WordPress.Security.NonceVerification.Recommended -- Highlighting the current tab only. $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : ''; $view = isset( $_GET['view'] ) ? sanitize_key( wp_unslash( $_GET['view'] ) ) : ''; // phpcs:enable $tabs = array( 'docs-in-order' => __( 'Requests', 'docs-in-order' ) ); $tabs['docs-in-order-templates'] = __( 'Templates', 'docs-in-order' ); if ( current_user_can( 'manage_options' ) ) { $tabs['docs-in-order-settings'] = __( 'Settings', 'docs-in-order' ); $checks = DOCSINO_Health::checks(); $todo = count( $checks ) - DOCSINO_Health::passed( $checks ); /* translators: %d: number of setup items still to do. */ $tabs['docs-in-order-setup'] = $todo ? sprintf( __( 'Setup (%d to do)', 'docs-in-order' ), $todo ) : __( 'Setup', 'docs-in-order' ); // Before Pro: "Get Pro". Pro installed without a licence: "Activate Pro" (highlighted). // With Pro: the licence page (Account), when Freemius has one. $pro_state = DOCSINO_Admin_Pro_Page::state(); if ( 'active' === $pro_state && docsino_freemius_ready() && docsino_fs()->is_registered() ) { $tabs['docs-in-order-account'] = __( 'Pro', 'docs-in-order' ); } elseif ( 'inactive' === $pro_state ) { $tabs[ DOCSINO_Admin_Pro_Page::PAGE ] = __( 'Activate Pro', 'docs-in-order' ); $attention = DOCSINO_Admin_Pro_Page::PAGE; } else { $tabs[ DOCSINO_Admin_Pro_Page::PAGE ] = 'active' === $pro_state ? __( 'Pro', 'docs-in-order' ) : __( 'Get Pro', 'docs-in-order' ); } } /** * App bar tabs: page slug => label. DocsInOrder Pro adds "Team". * * @param array $tabs Tabs. */ $tabs = (array) apply_filters( 'docsino_app_bar_tabs', $tabs ); ?> review_prompt(); } /** * One-time "Enjoying DocsInOrder?" prompt after a few completed requests. */ private function review_prompt() { if ( ! current_user_can( 'manage_options' ) ) { return; } $state = get_user_meta( get_current_user_id(), 'docsino_review_prompt', true ); if ( 'done' === $state || ( is_numeric( $state ) && (int) $state > time() ) ) { return; } if ( self::completed_requests() < self::REVIEW_PROMPT_AFTER ) { return; } ?> get_var( $wpdb->prepare( "SELECT COUNT(*) FROM %i WHERE status = 'completed'", $table ) ); } /** * "Setup" and "Settings" links under the plugin name on the Plugins screen. * * @param string[] $links Action links. * @return string[] */ public function action_links( $links ) { if ( current_user_can( 'manage_options' ) ) { array_unshift( $links, '' . esc_html__( 'Setup', 'docs-in-order' ) . '', '' . esc_html__( 'Settings', 'docs-in-order' ) . '' ); } return $links; } /** * "Get help" and "Rate ★★★★★" next to "View details" on the Plugins screen. * * @param string[] $meta Row meta links. * @param string $file Plugin file. * @return string[] */ public function row_meta( $meta, $file ) { if ( plugin_basename( DOCSINO_PLUGIN_FILE ) !== $file ) { return $meta; } $meta[] = '' . esc_html__( 'Get help', 'docs-in-order' ) . ''; $meta[] = '' . esc_html__( 'Rate', 'docs-in-order' ) . ' '; return $meta; } /** * The admin menu icon: logo A in one colour, as a data URI. * WordPress recolours single-colour SVG menu icons to match the admin colour scheme. * * @return string */ public static function menu_icon() { $svg = ''; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode -- Data URI for the menu icon. return 'data:image/svg+xml;base64,' . base64_encode( $svg ); } /** * The full-colour logo icon (direction A) as inline SVG. * * @param int $size Width and height in pixels. * @return string */ public static function logo_svg( $size = 32, $light = false ) { if ( $light ) { // For dark (teal) backgrounds. return sprintf( '', (int) $size ); } return sprintf( '', (int) $size ); } /** * URL of a DocsInOrder admin screen. * * @param string $page Page slug. * @param array $args Extra query args. * @return string */ public static function page_url( $page = 'docs-in-order', array $args = array() ) { return add_query_arg( array_merge( array( 'page' => $page ), $args ), admin_url( 'admin.php' ) ); } /** * URL of a request's review screen. * * @param int $checklist_id Request ID. * @param array $args Extra query args. * @return string */ public static function review_url( $checklist_id, array $args = array() ) { return self::page_url( 'docs-in-order', array_merge( array( 'view' => 'request', 'id' => (int) $checklist_id, ), $args ) ); } /** * Admin-only URL that downloads a stored client file. * * @param int $upload_id Upload ID. * @return string */ public static function download_url( $upload_id ) { return self::file_url( 'docsino_download', $upload_id ); } /** * Admin-only URL that shows a stored PDF or image in the browser. * * @param int $upload_id Upload ID. * @return string */ public static function view_url( $upload_id ) { return self::file_url( 'docsino_view', $upload_id ); } /** * Nonce-protected admin-post URL for a file action. * * @param string $action docsino_download or docsino_view. * @param int $upload_id Upload ID. * @return string */ private static function file_url( $action, $upload_id ) { return wp_nonce_url( add_query_arg( array( 'action' => $action, 'upload_id' => (int) $upload_id, ), admin_url( 'admin-post.php' ) ), $action . '_' . (int) $upload_id ); } /** * Opening tag and hidden fields of a form that posts to admin-post.php. * * @param string $action admin-post action. * @param string $nonce_action Nonce action. * @param array $fields Extra hidden fields. * @param string $class Form CSS class. */ public static function form_open( $action, $nonce_action, array $fields = array(), $class = '' ) { printf( '