'dropdms', 'action' => 'edit', 'id' => (int) $id, ), admin_url( 'admin.php' ) ); } /** * File URL (REST, with nonce for the cookie authentication). * * @param int $id Document ID. * @param bool $inline Show in the browser. */ public static function file_url( $id, $inline ) { $args = array( '_wpnonce' => wp_create_nonce( 'wp_rest' ) ); if ( $inline ) { $args['inline'] = 1; } return add_query_arg( $args, rest_url( DropDMS_Rest::NS . '/documents/' . (int) $id . '/file' ) ); } /** * Thumbnail URL. * * @param int $id Document ID. */ public static function thumb_url( $id ) { return add_query_arg( '_wpnonce', wp_create_nonce( 'wp_rest' ), rest_url( DropDMS_Rest::NS . '/documents/' . (int) $id . '/thumb' ) ); } /** * Delete URL with nonce. * * @param int $id Document ID. */ public static function delete_url( $id ) { return wp_nonce_url( add_query_arg( array( 'action' => 'dropdms_delete', 'id' => (int) $id, ), admin_url( 'admin-post.php' ) ), 'dropdms_delete_' . (int) $id ); } /** * Formats a UTC datetime from the database in the site time zone and language. * * @param string $gmt Y-m-d H:i:s (UTC). */ public static function local_date( $gmt ) { return mysql2date( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), get_date_from_gmt( $gmt ) ); } // ------------------------------------------------------------------ Menu /** Admin menu. */ public static function menu() { self::$page_hook = add_menu_page( __( 'DropDMS – Documents', 'dropdms' ), 'DropDMS', 'dropdms_view', 'dropdms', array( __CLASS__, 'render_documents_page' ), 'dashicons-media-document', 26 ); add_submenu_page( 'dropdms', __( 'Documents', 'dropdms' ), __( 'Documents', 'dropdms' ), 'dropdms_view', 'dropdms', array( __CLASS__, 'render_documents_page' ) ); add_submenu_page( 'dropdms', __( 'Categories', 'dropdms' ), __( 'Categories', 'dropdms' ), 'dropdms_manage', 'edit-tags.php?taxonomy=' . DropDMS_Categories::TAXONOMY ); self::$settings_hook = add_submenu_page( 'dropdms', __( 'DropDMS Settings', 'dropdms' ), __( 'Settings', 'dropdms' ), 'dropdms_manage', 'dropdms-settings', array( __CLASS__, 'render_settings_page' ) ); add_action( 'load-' . self::$page_hook, array( __CLASS__, 'load_documents_page' ) ); } /** * Highlights the DropDMS menu on the category screens. * * @param string $parent_file Parent menu file. */ public static function parent_file( $parent_file ) { global $submenu_file; $screen = get_current_screen(); if ( $screen && DropDMS_Categories::TAXONOMY === $screen->taxonomy ) { $submenu_file = 'edit-tags.php?taxonomy=' . DropDMS_Categories::TAXONOMY; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- intended to highlight the submenu. return 'dropdms'; } return $parent_file; } /** * Saves the "documents per page" screen option. * * @param mixed $status Value to save or false. * @param string $option Option name. * @param int $value New value. */ public static function set_screen_option( $status, $option, $value ) { return 'dropdms_per_page' === $option ? min( 200, max( 1, (int) $value ) ) : $status; } /** * Settings link on the plugins screen. * * @param array $links Links. */ public static function action_links( $links ) { if ( current_user_can( 'dropdms_manage' ) ) { array_unshift( $links, '' . esc_html__( 'Settings', 'dropdms' ) . '' ); } return $links; } // ------------------------------------------------------------------ Assets /** * Loads CSS/JS on the DropDMS pages. * * @param string $hook Hook suffix. */ public static function enqueue( $hook ) { if ( $hook !== self::$page_hook && $hook !== self::$settings_hook ) { return; } wp_enqueue_style( 'dropdms-admin', DROPDMS_URL . 'assets/css/admin.css', array(), DROPDMS_VERSION ); if ( $hook !== self::$page_hook ) { return; } self::register_upload_script(); wp_enqueue_script( 'dropdms-upload' ); } /** Registers the upload/OCR script with its options (also used by the Pro add-on). */ public static function register_upload_script() { if ( wp_script_is( 'dropdms-upload', 'registered' ) ) { return; } $vendor = DROPDMS_URL . 'assets/vendor/'; wp_register_script( 'dropdms-upload', DROPDMS_URL . 'assets/js/upload.js', array( 'wp-i18n' ), DROPDMS_VERSION, true ); wp_set_script_translations( 'dropdms-upload', 'dropdms', DROPDMS_DIR . 'languages' ); $options = array( 'restUrl' => esc_url_raw( rest_url( DropDMS_Rest::NS . '/' ) ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'editUrl' => esc_url_raw( admin_url( 'admin.php?page=dropdms&action=edit&id=' ) ), 'maxBytes' => DropDMS_Settings::max_upload_bytes(), 'maxPages' => (int) DropDMS_Settings::get( 'pdf_max_pages' ), 'ocrLang' => (string) DropDMS_Settings::get( 'ocr_lang' ), // Bundled libraries (no CDN): Tesseract.js 5.1.1, tesseract.js-core 5.1.1, tessdata_fast 4.1.0 (as JSON), // pdf.js 4.10.38. 'vendor' => array( 'tesseract' => $vendor . 'tesseract/tesseract.min.js', 'worker' => $vendor . 'tesseract/worker.min.js', 'core' => $vendor . 'tesseract-core/', 'lang' => $vendor . 'tessdata/', 'pdfjs' => $vendor . 'pdfjs/pdf.min.js', 'pdfjsWorker' => $vendor . 'pdfjs/pdf.worker.min.js', ), ); wp_add_inline_script( 'dropdms-upload', 'window.dropdmsUpload = ' . wp_json_encode( $options ) . ';', 'before' ); } // ------------------------------------------------------------------ Documents page /** Runs before the documents page is output: screen options and bulk actions. */ public static function load_documents_page() { add_screen_option( 'per_page', array( 'label' => __( 'Documents per page', 'dropdms' ), 'default' => 20, 'option' => 'dropdms_per_page', ) ); self::handle_bulk(); self::$table = new DropDMS_List_Table(); } /** Bulk delete from the list (GET form of WP_List_Table, nonce "bulk-documents"). */ private static function handle_bulk() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- the nonce is checked below before acting. $action = isset( $_REQUEST['action'] ) ? sanitize_key( wp_unslash( $_REQUEST['action'] ) ) : ''; if ( '-1' === $action || '' === $action ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = isset( $_REQUEST['action2'] ) ? sanitize_key( wp_unslash( $_REQUEST['action2'] ) ) : ''; } if ( 'delete' !== $action || empty( $_REQUEST['document'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } check_admin_referer( 'bulk-documents' ); if ( ! current_user_can( 'dropdms_delete' ) ) { wp_die( esc_html__( 'You are not allowed to delete documents.', 'dropdms' ), 403 ); } $ids = array_map( 'absint', (array) wp_unslash( $_REQUEST['document'] ) ); $deleted = DropDMS_Documents::delete( $ids ); wp_safe_redirect( add_query_arg( array( 'page' => 'dropdms', 'deleted' => $deleted, ), admin_url( 'admin.php' ) ) ); exit; } /** Output of the documents page (list or edit screen). */ public static function render_documents_page() { // phpcs:disable WordPress.Security.NonceVerification.Recommended -- read-only view parameters. $action = isset( $_GET['action'] ) ? sanitize_key( wp_unslash( $_GET['action'] ) ) : ''; $id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0; // phpcs:enable if ( 'edit' === $action && $id ) { self::render_edit( $id ); return; } $table = self::$table ? self::$table : new DropDMS_List_Table(); $table->prepare_items(); $filters = DropDMS_List_Table::filters(); echo '
'; echo '

' . esc_html__( 'Documents', 'dropdms' ) . '

'; echo '
'; self::notices(); if ( current_user_can( 'dropdms_upload' ) ) { self::render_dropzone(); } /** * Fires above the document list (e.g. for buttons of the Pro add-on). */ do_action( 'dropdms_before_list' ); echo '
'; echo ''; $table->search_box( __( 'Search documents', 'dropdms' ), 'dropdms-search' ); $table->display(); echo '
'; if ( '' !== $filters['search'] ) { echo '

' . esc_html( 'fulltext' === DropDMS_Settings::get( 'search_mode' ) ? __( 'Full-text search: finds word beginnings in title, file name, note and recognized text.', 'dropdms' ) : __( 'Partial word search in title, file name, note and recognized text. All terms must match.', 'dropdms' ) ) . '

'; } echo '
'; } /** Drop zone with category selection and upload queue. */ private static function render_dropzone() { $max = DropDMS_Settings::max_upload_bytes(); ?>

' . esc_html( $text ) . '

'; } if ( isset( $_GET['saved'] ) ) { echo '

' . esc_html__( 'Document saved.', 'dropdms' ) . '

'; } // phpcs:enable } // ------------------------------------------------------------------ Edit screen /** * Edit screen of a document (read-only without dropdms_edit). * * @param int $id Document ID. */ private static function render_edit( $id ) { $doc = DropDMS_Documents::get( $id ); if ( ! $doc ) { echo '

' . esc_html__( 'Document not found.', 'dropdms' ) . '

'; return; } $can_edit = current_user_can( 'dropdms_edit' ); $readonly = $can_edit ? '' : ' readonly'; $user = get_userdata( (int) $doc->created_by ); $back = admin_url( 'admin.php?page=dropdms' ); ?>


id ); ?>
filename ); ?>
mime . ' · ' . size_format( (int) $doc->filesize, 1 ) ); ?>
created ) . ( $user ? ' · ' . $user->display_name : '' ) ); ?>
modified ) : ?>
modified ) ); ?>
sha256 ); ?>

id, true ); if ( 'application/pdf' === $doc->mime ) { printf( '', esc_url( $url ), esc_attr( $doc->title ) ); } else { printf( '%s', esc_url( $url ), esc_attr( $doc->title ) ); } } /** Saves the edit form. */ public static function handle_save() { $id = isset( $_POST['id'] ) ? absint( $_POST['id'] ) : 0; check_admin_referer( 'dropdms_save_' . $id ); if ( ! current_user_can( 'dropdms_edit' ) ) { wp_die( esc_html__( 'You are not allowed to edit documents.', 'dropdms' ), 403 ); } if ( ! DropDMS_Documents::get( $id ) ) { wp_die( esc_html__( 'Document not found.', 'dropdms' ), 404 ); } $title = isset( $_POST['title'] ) ? sanitize_text_field( wp_unslash( $_POST['title'] ) ) : ''; $category = isset( $_POST['category'] ) ? absint( $_POST['category'] ) : 0; $date = isset( $_POST['document_date'] ) ? sanitize_text_field( wp_unslash( $_POST['document_date'] ) ) : ''; $valid = preg_match( '/^(\d{4})-(\d{2})-(\d{2})$/', $date, $m ) && checkdate( (int) $m[2], (int) $m[3], (int) $m[1] ); DropDMS_Documents::update( $id, array( 'title' => '' !== $title ? mb_substr( $title, 0, 255 ) : __( 'Untitled', 'dropdms' ), 'category_id' => DropDMS_Categories::exists( $category ) ? $category : 0, 'status' => empty( $_POST['status'] ) ? 0 : 1, 'document_date' => $valid ? $date : null, // Note and recognized text are plain text and may contain "<" etc.; they are cleaned of control // characters and always escaped on output. 'note' => isset( $_POST['note'] ) ? mb_substr( DropDMS_Documents::clean_text( wp_unslash( $_POST['note'] ) ), 0, 65000 ) : '', // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 'ocr_text' => isset( $_POST['ocr_text'] ) ? DropDMS_Documents::clean_text( wp_unslash( $_POST['ocr_text'] ) ) : '', // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ) ); wp_safe_redirect( add_query_arg( 'saved', 1, self::edit_url( $id ) ) ); exit; } /** Deletes one document (row action / edit screen). */ public static function handle_delete() { $id = isset( $_GET['id'] ) ? absint( $_GET['id'] ) : 0; check_admin_referer( 'dropdms_delete_' . $id ); if ( ! current_user_can( 'dropdms_delete' ) ) { wp_die( esc_html__( 'You are not allowed to delete documents.', 'dropdms' ), 403 ); } $deleted = DropDMS_Documents::delete( array( $id ) ); wp_safe_redirect( add_query_arg( array( 'page' => 'dropdms', 'deleted' => $deleted, ), admin_url( 'admin.php' ) ) ); exit; } // ------------------------------------------------------------------ Settings /** Settings API registration. */ public static function register_settings() { register_setting( 'dropdms_settings', DropDMS_Settings::OPTION, array( 'type' => 'array', 'sanitize_callback' => array( 'DropDMS_Settings', 'sanitize' ), 'default' => DropDMS_Settings::defaults(), ) ); add_settings_section( 'dropdms_upload', __( 'Upload and text recognition', 'dropdms' ), '__return_false', 'dropdms-settings' ); add_settings_section( 'dropdms_search', __( 'Search', 'dropdms' ), '__return_false', 'dropdms-settings' ); add_settings_section( 'dropdms_data', __( 'Data', 'dropdms' ), '__return_false', 'dropdms-settings' ); $fields = array( 'max_upload_mb' => array( __( 'Maximum file size (MB)', 'dropdms' ), 'dropdms_upload' ), 'ocr_lang' => array( __( 'Recognition language', 'dropdms' ), 'dropdms_upload' ), 'pdf_max_pages' => array( __( 'PDF pages to read', 'dropdms' ), 'dropdms_upload' ), 'search_mode' => array( __( 'Search mode', 'dropdms' ), 'dropdms_search' ), 'delete_on_uninstall' => array( __( 'On uninstall', 'dropdms' ), 'dropdms_data' ), ); foreach ( $fields as $key => $field ) { add_settings_field( 'dropdms_' . $key, $field[0], array( __CLASS__, 'render_field' ), 'dropdms-settings', $field[1], array( 'key' => $key, 'label_for' => 'dropdms_' . $key, ) ); } } /** * One settings field. * * @param array $args Field arguments. */ public static function render_field( $args ) { $key = $args['key']; $value = DropDMS_Settings::get( $key ); $name = DropDMS_Settings::OPTION . '[' . $key . ']'; $id = 'dropdms_' . $key; switch ( $key ) { case 'max_upload_mb': printf( '', esc_attr( $id ), esc_attr( $name ), (int) $value ); echo '

' . esc_html( sprintf( /* translators: %s: effective limit, e.g. "16 MB" */ __( 'Effective limit including PHP and database limits: %s.', 'dropdms' ), size_format( DropDMS_Settings::max_upload_bytes() ) ) ) . '

'; break; case 'ocr_lang': $labels = array( 'deu+eng' => __( 'German + English', 'dropdms' ), 'deu' => __( 'German', 'dropdms' ), 'eng' => __( 'English', 'dropdms' ), ); printf( ''; echo '

' . esc_html__( 'Used for scans and images. PDFs with a text layer are read directly.', 'dropdms' ) . '

'; break; case 'pdf_max_pages': printf( '', esc_attr( $id ), esc_attr( $name ), (int) $value ); break; case 'search_mode': printf( ''; break; case 'delete_on_uninstall': printf( '', esc_attr( $id ), esc_attr( $name ), checked( (int) $value, 1, false ), esc_html__( 'Delete all documents, categories and settings when the plugin is deleted', 'dropdms' ) ); echo '

' . esc_html__( 'Off by default, so deleting the plugin by mistake does not destroy your archive.', 'dropdms' ) . '

'; break; } } /** Settings page including storage usage. */ public static function render_settings_page() { if ( ! current_user_can( 'dropdms_manage' ) ) { return; } $usage = DropDMS_Storage::usage(); $packet = DropDMS_Storage::max_packet(); ?>

' . esc_html( sprintf( __( 'Storage optimized, %s freed.', 'dropdms' ), size_format( $freed ) ) ) . '

'; } if ( isset( $_GET['optimize_error'] ) ) { echo '

' . esc_html__( 'The storage could not be optimized. Your database user may lack the permission for OPTIMIZE TABLE.', 'dropdms' ) . '

'; } // phpcs:enable ?>

max_allowed_packet
upload_max_filesize / post_max_size

'dropdms-settings' ); try { DropDMS_Storage::optimize(); $after = DropDMS_Storage::usage(); $args['optimized'] = max( 0, $before['allocated'] - $after['allocated'] ); } catch ( Throwable $e ) { $args['optimize_error'] = 1; } wp_safe_redirect( add_query_arg( $args, admin_url( 'admin.php' ) ) ); exit; } }