Fügen Sie mehrere Plugin-Verzeichnisse hinzu

39

Die Aufgabe

Sie können registrieren, indem register_theme_directory()Sie zusätzliche Themes-Verzeichnisse für Ihre WP-Installation hinzufügen . Leider bietet core nicht die gleiche Funktionalität für Plugins. Wir haben bereits MU-Plugins, Drop-Ins, Plugins und Themes. Für eine bessere Dateiorganisation benötigen wir jedoch mehr.

Hier ist die Liste der Aufgaben, die zu erfüllen sind:

  • Fügen Sie ein zusätzliches Plugin-Verzeichnis hinzu
  • Für jedes Plugin-Verzeichnis wird ein neuer "Tab" benötigt, wie hier gezeigt [1].
  • Das zusätzliche Verzeichnis hätte die gleiche Funktionalität wie das Standard-Plugin-Verzeichnis

Was ist für dich drin?

Die beste und vollständigste Antwort wird mit einem Kopfgeld belohnt.


[1] Zusätzliche Registerkarte für einen neuen Plugin-Ordner / -Verzeichnis

Kaiser
quelle
3
Da die Verzeichnisstruktur ziemlich eng mit Verzeichniskonstanten verbunden ist, habe ich Zweifel, dass dies auf Dateisystemebene praktisch ist (ohne Kernübernahme also). Die virtuelle Organisationsebene in admin ist möglicherweise auf Erweiterungsebene einfacher zu erreichen.
Rarst
@Rarst welche nicht halten Sie sich aus der Addition Ihre Gedanken zurück :)
kaiser
Dies wäre eine großartige Funktion.
ltfishie
Feature hört sich gut an. Sie müssen nur den Kern zurückentwickeln, herausfinden, wie es gemacht werden soll (auf die WP-Art) und dann einen Patch an die Entwickler senden () - get_theme () - get_themes ()
Sterling Hamilton
2
Jungs: Was einreichen ? Dies ist eine Frage, keine Antwort mit vollständigem Code :) Zu Ihrer Information: Ein neues Ticket auf trac zum Umschreibenget_themes() in eine Klasse.
Kaiser

Antworten:

28

Okay, ich werde es versuchen. Einige Einschränkungen, auf die ich unterwegs gestoßen bin:

  1. Es gibt nicht viele Filter in Unterklassen von WP_List_Table, zumindest nicht dort, wo wir sie brauchen.

  2. Aufgrund des Fehlens von Filtern können wir oben keine genaue Liste der Plugin-Typen führen.

  3. Wir müssen auch einige großartige (read: dirty) JavaScript-Hacks verwenden, um Plugins als aktiv anzuzeigen.

Ich habe meine Admin-Vorwahl in eine Klasse eingeschlossen, damit meinen Funktionsnamen kein Präfix vorangestellt wird. Sie können den gesamten Code hier sehen . Bitte tragen Sie bei!

Zentrale API

Nur eine einfache Funktion, die eine globale Variable erstellt, die unsere Plugin-Verzeichnisse in einem assoziativen Array enthält. Das $keywird intern verwendet, um Plugins abzurufen, usw. $dirist entweder ein vollständiger Pfad oder etwas relativ zum wp-contentVerzeichnis. $labelwird für unsere Anzeige im Admin-Bereich sein (zB eine übersetzbare Zeichenfolge).

<?php
function register_plugin_directory( $key, $dir, $label )
{
    global $wp_plugin_directories;
    if( empty( $wp_plugin_directories ) ) $wp_plugin_directories = array();

    if( ! file_exists( $dir ) && file_exists( trailingslashit( WP_CONTENT_DIR ) . $dir ) )
    {
        $dir = trailingslashit( WP_CONTENT_DIR ) . $dir;
    }

    $wp_plugin_directories[$key] = array(
        'label' => $label,
        'dir'   => $dir
    );
}

Dann müssen wir natürlich die Plugins laden. Hängen Sie plugins_loadedsich spät ein und gehen Sie die aktiven Plugins durch und laden Sie sie.

Admin-Bereich

Lassen Sie uns unsere Funktionalität in einer Klasse einrichten.

<?php
class CD_APD_Admin
{

    /**
     * The container for all of our custom plugins
     */
    protected $plugins = array();

    /**
     * What custom actions are we allowed to handle here?
     */
    protected $actions = array();

    /**
     * The original count of the plugins
     */
    protected $all_count = 0;

    /**
     * constructor
     * 
     * @since 0.1
     */
    function __construct()
    {
        add_action( 'load-plugins.php', array( &$this, 'init' ) );
        add_action( 'plugins_loaded', array( &$this, 'setup_actions' ), 1 );

    }

} // end class

Wir werden plugins_loadedsehr früh einsteigen und die erlaubten "Aktionen" einrichten, die wir verwenden werden. Diese behandeln die Aktivierung und Deaktivierung des Plugins, da die eingebauten Funktionen dies mit benutzerdefinierten Verzeichnissen nicht tun können.

function setup_actions()
{
    $tmp = array(
        'custom_activate',
        'custom_deactivate'
    );
    $this->actions = apply_filters( 'custom_plugin_actions', $tmp );
}

Dann ist da noch die Funktion, in die man sich einhakt load-plugins.php. Das macht alle möglichen lustigen Sachen.

function init()
{
    global $wp_plugin_directories;

    $screen = get_current_screen();

    $this->get_plugins();

    $this->handle_actions();

    add_filter( 'views_' . $screen->id, array( &$this, 'views' ) );

    // check to see if we're using one of our custom directories
    if( $this->get_plugin_status() )
    {
        add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
        add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
        // TODO: support bulk actions
        add_filter( 'bulk_actions-' . $screen->id, '__return_empty_array' );
        add_filter( 'plugin_action_links', array( &$this, 'action_links' ), 10, 2 );
        add_action( 'admin_enqueue_scripts', array( &$this, 'scripts' ) );
    }
}

Lassen Sie uns diese eine Sache nach der anderen durchgehen. Die get_pluginsMethode ist ein Wrapper um eine andere Funktion. Es füllt das Attribut pluginsmit Daten.

function get_plugins()
{
    global $wp_plugin_directories;
    foreach( array_keys( $wp_plugin_directories ) as $key )
    {
       $this->plugins[$key] = cd_apd_get_plugins( $key );
    }
}

cd_apd_get_pluginsist eine Kopie der eingebauten get_pluginsFunktion ohne Hardcodierung WP_CONTENT_DIRund pluginsBusiness. Grundsätzlich gilt: Holen Sie sich das Verzeichnis aus dem $wp_plugin_directoriesglobalen, öffnen Sie es, finden Sie alle Plugin-Dateien. Speichern Sie sie für später im Cache.

<?php
function cd_apd_get_plugins( $dir_key ) 
{
    global $wp_plugin_directories;

    // invalid dir key? bail
    if( ! isset( $wp_plugin_directories[$dir_key] ) )
    {
        return array();
    }
    else
    {
        $plugin_root = $wp_plugin_directories[$dir_key]['dir'];
    }

    if ( ! $cache_plugins = wp_cache_get( 'plugins', 'plugins') )
        $cache_plugins = array();

    if ( isset( $cache_plugins[$dir_key] ) )
        return $cache_plugins[$dir_key];

    $wp_plugins = array();

    $plugins_dir = @ opendir( $plugin_root );
    $plugin_files = array();
    if ( $plugins_dir ) {
        while ( ( $file = readdir( $plugins_dir ) ) !== false ) {
            if ( substr($file, 0, 1) == '.' )
                continue;
            if ( is_dir( $plugin_root.'/'.$file ) ) {
                $plugins_subdir = @ opendir( $plugin_root.'/'.$file );
                if ( $plugins_subdir ) {
                    while (($subfile = readdir( $plugins_subdir ) ) !== false ) {
                        if ( substr($subfile, 0, 1) == '.' )
                            continue;
                        if ( substr($subfile, -4) == '.php' )
                            $plugin_files[] = "$file/$subfile";
                    }
                    closedir( $plugins_subdir );
                }
            } else {
                if ( substr($file, -4) == '.php' )
                    $plugin_files[] = $file;
            }
        }
        closedir( $plugins_dir );
    }

    if ( empty($plugin_files) )
        return $wp_plugins;

    foreach ( $plugin_files as $plugin_file ) {
        if ( !is_readable( "$plugin_root/$plugin_file" ) )
            continue;

        $plugin_data = get_plugin_data( "$plugin_root/$plugin_file", false, false ); //Do not apply markup/translate as it'll be cached.

        if ( empty ( $plugin_data['Name'] ) )
            continue;

        $wp_plugins[trim( $plugin_file )] = $plugin_data;
    }

    uasort( $wp_plugins, '_sort_uname_callback' );

    $cache_plugins[$dir_key] = $wp_plugins;
    wp_cache_set('plugins', $cache_plugins, 'plugins');

    return $wp_plugins;
}

Als nächstes geht es darum, Plugins tatsächlich zu aktivieren und zu deaktivieren. Dazu verwenden wir die handle_actionsMethode. Dies ist wieder krass von der Spitze der Kerndatei abgerissen wp-admin/plugins.php.

function handle_actions()
{
    $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';

    // not allowed to handle this action? bail.
    if( ! in_array( $action, $this->actions ) ) return;

    // Get the plugin we're going to activate
    $plugin = isset( $_REQUEST['plugin'] ) ? $_REQUEST['plugin'] : false;
    if( ! $plugin ) return;

    $context = $this->get_plugin_status();

    switch( $action )
    {
        case 'custom_activate':
            if( ! current_user_can('activate_plugins') )
                    wp_die( __('You do not have sufficient permissions to manage plugins for this site.') );

            check_admin_referer( 'custom_activate-' . $plugin );

            $result = cd_apd_activate_plugin( $plugin, $context );
            if ( is_wp_error( $result ) ) 
            {
                if ( 'unexpected_output' == $result->get_error_code() ) 
                {
                    $redirect = add_query_arg( 'plugin_status', $context, self_admin_url( 'plugins.php' ) );
                    wp_redirect( add_query_arg( '_error_nonce', wp_create_nonce( 'plugin-activation-error_' . $plugin ), $redirect ) ) ;
                    exit();
                } 
                else 
                {
                    wp_die( $result );
                }
            }

            wp_redirect( add_query_arg( array( 'plugin_status' => $context, 'activate' => 'true' ), self_admin_url( 'plugins.php' ) ) );
            exit();
            break;
        case 'custom_deactivate':
            if ( ! current_user_can( 'activate_plugins' ) )
                wp_die( __('You do not have sufficient permissions to deactivate plugins for this site.') );

            check_admin_referer('custom_deactivate-' . $plugin);
            cd_apd_deactivate_plugins( $plugin, $context );
            if ( headers_sent() )
                echo "<meta http-equiv='refresh' content='" . esc_attr( "0;url=plugins.php?deactivate=true&plugin_status=$status&paged=$page&s=$s" ) . "' />";
            else
                wp_redirect( self_admin_url("plugins.php?deactivate=true&plugin_status=$context") );
            exit();
            break;
        default:
            do_action( 'custom_plugin_dir_' . $action );
            break;
    }

}

Ein paar benutzerdefinierte Funktionen hier wieder. cd_apd_activate_plugin(abgerissen von activate_plugin) und cd_apd_deactivate_plugins(abgerissen von deactivate_plugins). Beide sind die gleichen wie ihre jeweiligen "Eltern" -Funktionen ohne die fest codierten Verzeichnisse.

function cd_apd_activate_plugin( $plugin, $context, $silent = false ) 
{
    $plugin = trim( $plugin );

    $redirect = add_query_arg( 'plugin_status', $context, admin_url( 'plugins.php' ) );
    $redirect = apply_filters( 'custom_plugin_redirect', $redirect );

    $current = get_option( 'active_plugins_' . $context, array() );

    $valid = cd_apd_validate_plugin( $plugin, $context );
    if ( is_wp_error( $valid ) )
        return $valid;

    if ( !in_array($plugin, $current) ) {
        if ( !empty($redirect) )
            wp_redirect(add_query_arg('_error_nonce', wp_create_nonce('plugin-activation-error_' . $plugin), $redirect)); // we'll override this later if the plugin can be included without fatal error
        ob_start();
        include_once( $valid );

        if ( ! $silent ) {
            do_action( 'custom_activate_plugin', $plugin, $context );
            do_action( 'custom_activate_' . $plugin, $context );
        }

        $current[] = $plugin;
        sort( $current );
        update_option( 'active_plugins_' . $context, $current );

        if ( ! $silent ) {
            do_action( 'custom_activated_plugin', $plugin, $context );
        }

        if ( ob_get_length() > 0 ) {
            $output = ob_get_clean();
            return new WP_Error('unexpected_output', __('The plugin generated unexpected output.'), $output);
        }
        ob_end_clean();
    }

    return true;
}

Und die Deaktivierungsfunktion

function cd_apd_deactivate_plugins( $plugins, $context, $silent = false ) {
    $current = get_option( 'active_plugins_' . $context, array() );

    foreach ( (array) $plugins as $plugin ) 
    {
        $plugin = trim( $plugin );
        if ( ! in_array( $plugin, $current ) ) continue;

        if ( ! $silent )
            do_action( 'custom_deactivate_plugin', $plugin, $context );

        $key = array_search( $plugin, $current );
        if ( false !== $key ) {
            array_splice( $current, $key, 1 );
        }

        if ( ! $silent ) {
            do_action( 'custom_deactivate_' . $plugin, $context );
            do_action( 'custom_deactivated_plugin', $plugin, $context );
        }
    }

    update_option( 'active_plugins_' . $context, $current );
}

Es gibt auch cd_apd_validate_pluginFunktionen, die natürlich eine Abzocke sind, validate_pluginohne den fest codierten Müll.

<?php
function cd_apd_validate_plugin( $plugin, $context ) 
{
    $rv = true;
    if ( validate_file( $plugin ) )
    {
        $rv = new WP_Error('plugin_invalid', __('Invalid plugin path.'));
    }

    global $wp_plugin_directories;
    if( ! isset( $wp_plugin_directories[$context] ) )
    {
        $rv = new WP_Error( 'invalid_context', __( 'The context for this plugin does not exist' ) );
    }

    $dir = $wp_plugin_directories[$context]['dir'];
    if( ! file_exists( $dir . '/' . $plugin) )
    {
        $rv = new WP_Error( 'plugin_not_found', __( 'Plugin file does not exist.' ) );
    }

    $installed_plugins = cd_apd_get_plugins( $context );
    if ( ! isset($installed_plugins[$plugin]) )
    {
        $rv = new WP_Error( 'no_plugin_header', __('The plugin does not have a valid header.') );
    }

    $rv = $dir . '/' . $plugin;
    return $rv;
}

Okay, damit aus dem Weg. Wir können tatsächlich über die Anzeige der Listentabelle sprechen

Schritt 1: Fügen Sie unsere Ansichten zur Liste oben in der Tabelle hinzu. Dies geschieht durch Filtern views_{$screen->id}innerhalb unserer initFunktion.

add_filter( 'views_' . $screen->id, array( &$this, 'views' ) );

Dann durchläuft die eigentliche Hook-Funktion einfach die $wp_plugin_directories. Wenn eines der neu registrierten Verzeichnisse Plugins enthält, werden diese in die Anzeige aufgenommen.

function views( $views )
{
    global $wp_plugin_directories;

    // bail if we don't have any extra dirs
    if( empty( $wp_plugin_directories ) ) return $views;

    // Add our directories to the action links
    foreach( $wp_plugin_directories as $key => $info )
    {
        if( ! count( $this->plugins[$key] ) ) continue;
        $class = $this->get_plugin_status() == $key ? ' class="current" ' : '';
        $views[$key] = sprintf( 
            '<a href="%s"' . $class . '>%s <span class="count">(%d)</span></a>',
            add_query_arg( 'plugin_status', $key, 'plugins.php' ),
            esc_html( $info['label'] ),
            count( $this->plugins[$key] )
        );
    }
    return $views;
}

Das erste, was wir tun müssen, wenn wir eine benutzerdefinierte Plugin-Verzeichnisseite anzeigen, ist, die Ansichten erneut zu filtern. Wir müssen die inactiveZählung loswerden, weil sie nicht genau sein wird. Eine Folge davon, dass es keine Filter gibt, wo wir sie brauchen. Haken Sie wieder ein ...

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
}

Und ein kurzes Loslassen ...

function views_again( $views )
{
    if( isset( $views['inactive'] ) ) unset( $views['inactive'] );
    return $views;
}

Lassen Sie uns als Nächstes die Plugins entfernen, die Sie sonst in der Listentabelle gesehen hätten, und sie durch unsere benutzerdefinierten Plugins ersetzen. Haken Sie ein all_plugins.

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
    add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
}

Da wir bereits unsere Plugins und Daten eingerichtet haben (siehe setup_pluginsoben), filter_pluginsspeichert die Methode nur (1) die Anzahl aller Plugins für einen späteren Zeitpunkt und (2) ersetzt die Plugins in der Listentabelle.

function filter_plugins( $plugins )
{
    if( $key = $this->get_plugin_status() )
    {
        $this->all_count = count( $plugins );
        $plugins = $this->plugins[$key];
    }
    return $plugins;
}

Und jetzt werden wir die Massenaktionen töten. Diese könnten leicht unterstützt werden, nehme ich an?

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
    add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
    // TODO: support bulk actions
    add_filter( 'bulk_actions-' . $screen->id, '__return_empty_array' );
}

Die standardmäßigen Plugin-Aktionslinks funktionieren bei uns nicht. Also müssen wir stattdessen unsere eigenen einrichten (mit den benutzerdefinierten Aktionen usw.). In der initFunktion.

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
    add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
    // TODO: support bulk actions
    add_filter( 'bulk_actions-' . $screen->id, '__return_empty_array' );
    add_filter( 'plugin_action_links', array( &$this, 'action_links' ), 10, 2 );
}

Die einzigen Dinge, die hier geändert werden, sind: (1) Wir ändern die Aktionen, (2) den Plugin-Status beizubehalten und (3) die Nonce-Namen ein wenig zu ändern.

function action_links( $links, $plugin_file )
{
    $context = $this->get_plugin_status();

    // let's just start over
    $links = array();
    $links['activate'] = sprintf(
        '<a href="%s" title="Activate this plugin">%s</a>',
        wp_nonce_url( 'plugins.php?action=custom_activate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . esc_attr( $context ), 'custom_activate-' . $plugin_file ),
        __( 'Activate' )
    );

    $active = get_option( 'active_plugins_' . $context, array() );
    if( in_array( $plugin_file, $active ) )
    {
        $links['deactivate'] = sprintf(
            '<a href="%s" title="Deactivate this plugin" class="cd-apd-deactivate">%s</a>',
            wp_nonce_url( 'plugins.php?action=custom_deactivate&amp;plugin=' . $plugin_file . '&amp;plugin_status=' . esc_attr( $context ), 'custom_deactivate-' . $plugin_file ),
            __( 'Deactivate' )
        );
    }
    return $links;
}

Und zum Schluss müssen wir nur noch JavaScript in die Warteschlange stellen, um das Ganze abzurunden. initWieder in der Funktion (diesmal alle zusammen).

if( $this->get_plugin_status() )
{
    add_filter( 'views_' . $screen->id, array( &$this, 'views_again' ) );
    add_filter( 'all_plugins', array( &$this, 'filter_plugins' ) );
    // TODO: support bulk actions
    add_filter( 'bulk_actions-' . $screen->id, '__return_empty_array' );
    add_filter( 'plugin_action_links', array( &$this, 'action_links' ), 10, 2 );
    add_action( 'admin_enqueue_scripts', array( &$this, 'scripts' ) );
}

Während wir unser JS in die Warteschlange stellen, verwenden wir auch wp_localize_script, um den Wert der Gesamtzahl aller Plugins abzurufen .

function scripts()
{
    wp_enqueue_script(
        'cd-apd-js',
        CD_APD_URL . 'js/apd.js',
        array( 'jquery' ),
        null
    );
    wp_localize_script(
        'cd-apd-js',
        'cd_apd',
        array(
            'count' => esc_js( $this->all_count )
        )
    );
}

Und natürlich ist der JS nur ein paar nette Hacks, um die Listentabelle von aktiven / inaktiven Plugins richtig anzuzeigen. Wir werden auch die korrekte Anzahl aller Plugins wieder in den AllLink einfügen.

jQuery(document).ready(function(){
    jQuery('li.all a').removeClass('current').find('span.count').html('(' + cd_apd.count + ')');
    jQuery('.wp-list-table.plugins tr').each(function(){
        var is_active = jQuery(this).find('a.cd-apd-deactivate');
        if(is_active.length) {
            jQuery(this).removeClass('inactive').addClass('active');
            jQuery(this).find('div.plugin-version-author-uri').removeClass('inactive').addClass('active');
        }
    });
});

Einpacken

Das eigentliche Laden zusätzlicher Plugin-Verzeichnisse ist ziemlich aufregend. Schwieriger ist es, die Listentabelle korrekt anzuzeigen. Ich bin immer noch nicht ganz zufrieden mit dem Ergebnis, aber vielleicht kann jemand den Code verbessern

chrisguitarguy
quelle
1
Beeindruckend! Wirklich gute Arbeit. Ich werde mir über das Wochenende etwas Zeit nehmen, um Ihren Code zu studieren. Hinweis: Es gibt eine Funktion __return_empty_array().
fuxia
Vielen Dank! Feedback ist immer willkommen. __return_empty_arrayFunktion eingebaut !
Chrisguitarguy
1
Sie sollten eine Liste aller Stellen zusammenstellen, an denen ein einfacher Kernfilter Ihnen eine separate Funktion erspart hätte. Und dann… ein Trac-Ticket einreichen.
Fuxia
Das ist wirklich toll. Es wäre noch cooler, wenn wir dies als Bibliothek innerhalb eines Themas darstellen könnten (siehe meinen Kommentar zu Github: github.com/chrisguitarguy/WP-Plugin-Directories/issues/4 )
julien_c
1
+1 Kann nicht glauben, dass ich diese Antwort verpasst habe - tolle Arbeit! Ich werde Ihren Code über das Wochenende genauer untersuchen :). @ Julien_c - warum würdest du das in einem Theme verwenden?
Stephen Harris
2

Ich persönlich habe kein Interesse daran, die Benutzeroberfläche zu ändern, aber ich würde aus mehreren Gründen ein besser organisiertes Dateisystem-Layout begrüßen.

Zu diesem Zweck wäre ein anderer Ansatz die Verwendung von Symlinks.

wp-content
    |-- plugins
        |-- acme-widgets               -> ../plugins-custom/acme-widgets
        |-- acme-custom-post-types     -> ../plugins-custom/acme-custom-post-types
        |-- acme-business-logic        -> ../plugins-custom/acme-business-logic
        |-- google-authenticator       -> ../plugins-external/google-authenticator
        |-- rest-api                   -> ../plugins-external/rest-api
        |-- quick-navigation-interface -> ../plugins-external/quick-navigation-interface
    |-- plugins-custom
        |-- acme-widgets
        |-- acme-custom-post-types
        |-- acme-business-logic
    |-- plugins-external
        |-- google-authenticator
        |-- rest-api
        |-- quick-navigation-interface

Sie können Ihre benutzerdefinierten Plugins einrichten plugins-custom, die Teil des Versionskontroll-Repository Ihres Projekts sein können.

Dann können Sie Abhängigkeiten von Drittanbietern installieren plugins-external(über Composer, Git-Submodule oder was auch immer Sie bevorzugen).

Dann können Sie ein einfaches Bash-Skript oder einen WP-CLI-Befehl verwenden, der die zusätzlichen Verzeichnisse durchsucht und pluginsfür jeden gefundenen Unterordner einen Symlink erstellt .

pluginswäre immer noch überladen, aber es wäre egal, weil Sie nur mit plugins-customund interagieren müssten plugins-external.

Das Skalieren in nzusätzliche Verzeichnisse würde dem gleichen Prozess folgen wie die ersten beiden.

Ian Dunn
quelle
-3

Sie können auch COMPOSER mit einem benutzerdefinierten Verzeichnispfad verwenden, der auf den Ordner wp-content verweist. Wenn es sich bei einer direkten Antwort auf Ihre Frage nicht um eine neue Denkweise von WordPress handelt, fahren Sie mit dem Komponisten fort, bevor er Sie auffrisst.

Franzscisco Mai
quelle
Vor langer Zeit zu Composer gewechselt. Bitte schauen Sie nach dem Datum dieser Frage. Abgesehen davon: Dies ist keine wirkliche Antwort. Vielleicht zeigen, wie man das tatsächlich einrichtet?
Kaiser