Custom WordPress plugin that replaces the default flat media library with a structured folder view. Features: hierarchical folders via custom taxonomy, sidebar folder tree, drag & drop, modal integration with Elementor/builders, bulk assign, upload auto-assign, toast notifications. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
219 lines
6.7 KiB
PHP
219 lines
6.7 KiB
PHP
<?php
|
|
/**
|
|
* Plugin Name: Media Folder Pro
|
|
* Plugin URI: https://www.project-pro.pl
|
|
* Description: Strukturyzowana biblioteka mediów z wirtualnymi folderami (podkatalogami).
|
|
* Version: 0.1.0
|
|
* Author: Project Pro
|
|
* Author URI: https://www.project-pro.pl
|
|
* License: GPL-2.0-or-later
|
|
* Text Domain: media-folder-pro
|
|
* Requires at least: 6.0
|
|
* Requires PHP: 8.0
|
|
*/
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
define( 'MFP_VERSION', '0.1.0' );
|
|
define( 'MFP_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
|
define( 'MFP_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
|
|
|
require_once MFP_PLUGIN_DIR . 'includes/class-taxonomy.php';
|
|
require_once MFP_PLUGIN_DIR . 'includes/class-ajax-handler.php';
|
|
require_once MFP_PLUGIN_DIR . 'includes/class-media-query.php';
|
|
|
|
final class Media_Folder_Pro {
|
|
|
|
private static ?self $instance = null;
|
|
private MFP_Taxonomy $taxonomy;
|
|
private MFP_Ajax_Handler $ajax;
|
|
private MFP_Media_Query $media_query;
|
|
|
|
public static function instance(): self {
|
|
if ( null === self::$instance ) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
private function __construct() {
|
|
$this->taxonomy = new MFP_Taxonomy();
|
|
$this->ajax = new MFP_Ajax_Handler( $this->taxonomy );
|
|
$this->media_query = new MFP_Media_Query();
|
|
|
|
add_action( 'init', [ $this->taxonomy, 'register' ] );
|
|
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
|
|
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_modal_assets' ], 20 );
|
|
add_action( 'wp_enqueue_media', [ $this, 'enqueue_modal_on_media_load' ] );
|
|
add_action( 'admin_footer-upload.php', [ $this, 'render_folder_tree_container' ] );
|
|
|
|
$this->ajax->register_hooks();
|
|
$this->media_query->register();
|
|
}
|
|
|
|
/**
|
|
* Shared localization data for all JS scripts.
|
|
*/
|
|
private function get_mfp_data(): array {
|
|
return [
|
|
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
|
|
'nonce' => wp_create_nonce( 'mfp_nonce' ),
|
|
'i18n' => [
|
|
'allMedia' => __( 'Wszystkie media', 'media-folder-pro' ),
|
|
'newFolder' => __( 'Nowy folder', 'media-folder-pro' ),
|
|
'folderName' => __( 'Nazwa folderu:', 'media-folder-pro' ),
|
|
'rename' => __( 'Zmień nazwę', 'media-folder-pro' ),
|
|
'delete' => __( 'Usuń', 'media-folder-pro' ),
|
|
'newSubfolder' => __( 'Nowy podfolder', 'media-folder-pro' ),
|
|
'confirmDelete' => __( 'Czy na pewno usunąć ten folder?', 'media-folder-pro' ),
|
|
'folderNotEmpty' => __( 'Folder nie jest pusty. Usuń najpierw zawartość.', 'media-folder-pro' ),
|
|
'error' => __( 'Wystąpił błąd. Spróbuj ponownie.', 'media-folder-pro' ),
|
|
'assignSuccess' => __( 'Media przypisane do folderu.', 'media-folder-pro' ),
|
|
'assignError' => __( 'Nie udało się przypisać mediów.', 'media-folder-pro' ),
|
|
'dropHere' => __( 'Upuść tutaj', 'media-folder-pro' ),
|
|
'uncategorized' => __( 'Bez folderu', 'media-folder-pro' ),
|
|
'moveToFolder' => __( 'Przenieś do folderu', 'media-folder-pro' ),
|
|
'removeFromFolder' => __( 'Usuń z folderu', 'media-folder-pro' ),
|
|
'noSelection' => __( 'Zaznacz media do przeniesienia', 'media-folder-pro' ),
|
|
'folderCreated' => __( 'Folder utworzony', 'media-folder-pro' ),
|
|
'folderRenamed' => __( 'Nazwa zmieniona', 'media-folder-pro' ),
|
|
'folderDeleted' => __( 'Folder usunięty', 'media-folder-pro' ),
|
|
'folderMoved' => __( 'Folder przeniesiony', 'media-folder-pro' ),
|
|
'emptyState' => __( 'Brak folderów', 'media-folder-pro' ),
|
|
'createFirst' => __( 'Utwórz pierwszy folder', 'media-folder-pro' ),
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Ensure mfpData is localized (registers inline script if tree JS not loaded).
|
|
*/
|
|
private function ensure_mfp_data(): void {
|
|
if ( wp_script_is( 'media-folder-pro-tree', 'enqueued' ) ) {
|
|
return;
|
|
}
|
|
wp_register_script( 'media-folder-pro-tree', false );
|
|
wp_enqueue_script( 'media-folder-pro-tree' );
|
|
wp_localize_script( 'media-folder-pro-tree', 'mfpData', $this->get_mfp_data() );
|
|
}
|
|
|
|
/**
|
|
* Full assets for Media Library pages (upload.php, media-new.php).
|
|
*/
|
|
public function enqueue_admin_assets( string $hook ): void {
|
|
if ( ! in_array( $hook, [ 'upload.php', 'media-new.php' ], true ) ) {
|
|
return;
|
|
}
|
|
|
|
wp_enqueue_style(
|
|
'media-folder-pro-admin',
|
|
MFP_PLUGIN_URL . 'assets/css/admin.css',
|
|
[],
|
|
MFP_VERSION
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'media-folder-pro-tree',
|
|
MFP_PLUGIN_URL . 'assets/js/folder-tree.js',
|
|
[],
|
|
MFP_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'media-folder-pro-filter',
|
|
MFP_PLUGIN_URL . 'assets/js/media-filter.js',
|
|
[ 'media-views', 'media-folder-pro-tree' ],
|
|
MFP_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_enqueue_script(
|
|
'media-folder-pro-modal',
|
|
MFP_PLUGIN_URL . 'assets/js/modal-integration.js',
|
|
[ 'media-views', 'media-folder-pro-tree' ],
|
|
MFP_VERSION,
|
|
true
|
|
);
|
|
|
|
wp_localize_script( 'media-folder-pro-tree', 'mfpData', $this->get_mfp_data() );
|
|
}
|
|
|
|
/**
|
|
* Modal integration for standard admin pages (post editor, etc.).
|
|
*/
|
|
public function enqueue_modal_assets( string $hook ): void {
|
|
if ( $hook === 'upload.php' || $hook === 'media-new.php' ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! did_action( 'wp_enqueue_media' ) && ! wp_script_is( 'media-views', 'enqueued' ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( wp_script_is( 'media-folder-pro-modal', 'enqueued' ) ) {
|
|
return;
|
|
}
|
|
|
|
wp_enqueue_style(
|
|
'media-folder-pro-admin',
|
|
MFP_PLUGIN_URL . 'assets/css/admin.css',
|
|
[],
|
|
MFP_VERSION
|
|
);
|
|
|
|
$this->ensure_mfp_data();
|
|
|
|
wp_enqueue_script(
|
|
'media-folder-pro-modal',
|
|
MFP_PLUGIN_URL . 'assets/js/modal-integration.js',
|
|
[ 'media-views', 'media-folder-pro-tree' ],
|
|
MFP_VERSION,
|
|
true
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook into wp_enqueue_media — fires whenever any plugin/theme calls wp_enqueue_media().
|
|
* Covers: Elementor, WPBakery, Divi, ACF, and any builder using wp.media.
|
|
*/
|
|
public function enqueue_modal_on_media_load(): void {
|
|
if ( wp_script_is( 'media-folder-pro-modal', 'enqueued' ) ) {
|
|
return;
|
|
}
|
|
|
|
// Skip pages handled by enqueue_admin_assets — wp_enqueue_media() fires
|
|
// BEFORE admin_enqueue_scripts on upload.php, which would poison the
|
|
// tree script handle with a false (empty) registration.
|
|
$screen = get_current_screen();
|
|
if ( $screen && in_array( $screen->base, [ 'upload', 'media' ], true ) ) {
|
|
return;
|
|
}
|
|
|
|
wp_enqueue_style(
|
|
'media-folder-pro-admin',
|
|
MFP_PLUGIN_URL . 'assets/css/admin.css',
|
|
[],
|
|
MFP_VERSION
|
|
);
|
|
|
|
$this->ensure_mfp_data();
|
|
|
|
wp_enqueue_script(
|
|
'media-folder-pro-modal',
|
|
MFP_PLUGIN_URL . 'assets/js/modal-integration.js',
|
|
[ 'media-views', 'media-folder-pro-tree' ],
|
|
MFP_VERSION,
|
|
true
|
|
);
|
|
}
|
|
|
|
public function render_folder_tree_container(): void {
|
|
echo '<div id="mfp-folder-root"></div>';
|
|
}
|
|
}
|
|
|
|
add_action( 'plugins_loaded', [ 'Media_Folder_Pro', 'instance' ] );
|