Skip to:
Content

Ticket #7157: 7157.patch

File 7157.patch, 39.0 KB (added by imath, 23 months ago)
  • src/bp-core/admin/bp-core-admin-functions.php

    diff --git src/bp-core/admin/bp-core-admin-functions.php src/bp-core/admin/bp-core-admin-functions.php
    index 39b54b2..c996e09 100644
    function bp_core_get_admin_tabs( $active_tab = '' ) { 
    438438                        'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-settings' ), 'admin.php' ) ),
    439439                        'name' => __( 'Options', 'buddypress' )
    440440                ),
     441                '3' => array(
     442                        'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bp-theme-packages' ), 'admin.php' ) ),
     443                        'name' => __( 'Template Pack', 'buddypress' )
     444                ),
    441445        );
    442446
    443447        // If forums component is active, add additional tab.
    function bp_core_get_admin_tabs( $active_tab = '' ) { 
    447451                wp_enqueue_script( 'thickbox' );
    448452                wp_enqueue_style( 'thickbox' );
    449453
    450                 $tabs['3'] = array(
     454                $tabs['4'] = array(
    451455                        'href' => bp_get_admin_url( add_query_arg( array( 'page' => 'bb-forums-setup'  ), 'admin.php' ) ),
    452456                        'name' => __( 'Forums', 'buddypress' )
    453457                );
  • src/bp-core/admin/bp-core-admin-theme-packages.php

    diff --git src/bp-core/admin/bp-core-admin-theme-packages.php src/bp-core/admin/bp-core-admin-theme-packages.php
    index e69de29..855306b 100644
     
     1<?php
     2/**
     3 * BuddyPress Theme Packages.
     4 *
     5 * @package BuddyPress
     6 * @subpackage Core
     7 * @since 2.7.0
     8 */
     9
     10// Exit if accessed directly.
     11defined( 'ABSPATH' ) || exit;
     12
     13/**
     14 * Get the possible Theme Packages locations
     15 *
     16 * @since  2.7.0
     17 *
     18 * @return array An associative array containing possible dirs and urls.
     19 */
     20function bp_core_admin_get_theme_packages_locations() {
     21        $bp = buddypress();
     22
     23        /**
     24         * Filter here to add your custom location.
     25         *
     26         * @since  2.7.0
     27         *
     28         * @param  array $value An associative array containing possible dirs and urls.
     29         */
     30        return (array) apply_filters( 'bp_core_admin_get_theme_packages_locations', array(
     31                'wp-content' => array(
     32                        'dir' => WP_CONTENT_DIR . '/bp-templates',
     33                        'url' => content_url() . '/bp-templates',
     34                        'id'  => 'wp-content',
     35                ),
     36                'bp-core'   => array(
     37                        'dir' => $bp->plugin_dir . 'bp-templates',
     38                        'url' => $bp->plugin_url . 'bp-templates',
     39                        'id'  => 'bp-core',
     40                ),
     41        ) );
     42}
     43
     44/**
     45 * Scan possible locations to find Temmplate Packs.
     46 *
     47 * @since  2.7.0
     48 *
     49 * @return array The found Template Packs in the available locations.
     50 */
     51function bp_core_admin_get_theme_packages() {
     52        $locations      = bp_core_admin_get_theme_packages_locations();
     53        $theme_packages = array();
     54
     55        if ( empty( $locations ) ) {
     56                return $theme_packages;
     57        }
     58
     59        // Loop through locations to get the available template packs.
     60        foreach( $locations as $location ) {
     61                if ( ! is_dir( $location['dir'] ) ) {
     62                        continue;
     63                }
     64
     65                $dirs = scandir( $location['dir'] );
     66
     67                foreach ( $dirs as $dir ) {
     68                        if ( 0 === strpos( $dir, '.' ) ) {
     69                                continue;
     70                        }
     71
     72                        // Avoid duplicates.
     73                        if ( isset( $theme_packages[ $dir ] ) || ! is_dir( $location['dir'] . '/' . $dir ) ) {
     74                                continue;
     75                        }
     76
     77                        $find_bp_functions = scandir( $location['dir'] . '/' . $dir );
     78
     79                        // The Template pack must have a 'buddypress-functions.php'
     80                        if ( ! array_search( 'buddypress-functions.php', $find_bp_functions ) ) {
     81                                continue;
     82                        }
     83
     84                        $tp      = new stdClass();
     85                        $tp->dir = trailingslashit( $location['dir'] ) . $dir;
     86                        $tp->url = trailingslashit( $location['url'] ) . wp_basename( $tp->dir );
     87
     88                        $theme_packages[ $dir ] = $tp;
     89                }
     90        }
     91
     92        return $theme_packages;
     93}
     94
     95/**
     96 * Translates the Theme Package Tags in the header of
     97 * the bootstrap file (buddypress-functions.php).
     98 *
     99 * @since  2.7.0
     100 *
     101 * @param  object $theme_package The Template Pack Data to translate.
     102 * @return object                The translated data.
     103 */
     104function bp_core_admin_translate_theme_package_header( $theme_package = null ) {
     105        if ( ! empty( $theme_package->text_domain ) ) {
     106                if ( ! empty( $theme_package->domain_path ) && ! empty( $theme_package->dir ) ) {
     107                        $locale      = apply_filters( 'buddypress_locale', get_locale(), $theme_package->text_domain );
     108                        $mofile      = $theme_package->dir . $theme_package->domain_path . sprintf( '%1$s-%2$s.mo', $theme_package->text_domain, $locale );
     109                        $translation = load_textdomain( $theme_package->text_domain, $mofile );
     110                }
     111
     112                if ( ! empty( $translation ) ) {
     113                        foreach ( array( 'name', 'version', 'description', 'author', 'link', 'supports' ) as $field ) {
     114                                $theme_package->{$field} = translate( $theme_package->{$field}, $theme_package->text_domain );
     115                        }
     116                }
     117        }
     118
     119        // Allowed tags for Name, author, version and desciption firlds
     120        $allowed_tags = $allowed_tags_no_links = array(
     121                'abbr'    => array( 'title' => true ),
     122                'acronym' => array( 'title' => true ),
     123                'code'    => true,
     124                'em'      => true,
     125                'strong'  => true,
     126        );
     127        $allowed_tags['a'] = array( 'href' => true, 'title' => true );
     128
     129        // Sanitize fields
     130        $theme_package->name        = wp_kses( $theme_package->name,    $allowed_tags_no_links );
     131        $theme_package->author      = wp_kses( $theme_package->author,  $allowed_tags );
     132        $theme_package->version     = wp_kses( $theme_package->version, $allowed_tags );
     133        $theme_package->description = wptexturize( wp_kses( $theme_package->description, $allowed_tags ) );
     134        $theme_package->link        = esc_url( $theme_package->link );
     135        $theme_package->supports    = esc_html( $theme_package->supports );
     136
     137        if ( isset( $theme_package->wp_required_version ) ) {
     138                $theme_package->wp_required_version = esc_html( $theme_package->wp_required_version );
     139        }
     140
     141        if ( isset( $theme_package->bp_required_version ) ) {
     142                $theme_package->bp_required_version = esc_html( $theme_package->bp_required_version );
     143        }
     144
     145        return $theme_package;
     146}
     147
     148/**
     149 * Gets the translated Theme Packages.
     150 *
     151 * @since  2.7.0
     152 *
     153 * @return array the translated Theme Packages.
     154 */
     155function bp_core_admin_get_translated_theme_packages() {
     156        $theme_packages = bp_core_admin_get_theme_packages();
     157
     158        if ( empty( $theme_packages ) ) {
     159                return $theme_packages;
     160        }
     161
     162        foreach( $theme_packages as $theme_package_id => $theme_package ) {
     163                if ( 'bp-legacy' === $theme_package_id ) {
     164                        $headers = array(
     165                                'id'          => 'legacy',
     166                                'name'        => __( 'BuddyPress Legacy', 'buddypress' ),
     167                                'version'     => bp_get_version(),
     168                                'description' => __( 'The BuddyPress legacy template pack', 'buddypress' ),
     169                                'author'      => __( 'The BuddyPress community', 'buddypress' ),
     170                                'link'        => 'https://buddypress.org',
     171                                'supports'    => _x(
     172                                        'activity, blogs, friends, groups, messages, notifications, retired-forums, settings, xprofile',
     173                                        'comma separated list of supported components',
     174                                        'buddypress'
     175                                ),
     176                                'text_domain' => 'buddypress',
     177                        );
     178                } else {
     179                        $headers = get_file_data( $theme_package->dir . '/buddypress-functions.php', array(
     180                                'id'                  => 'Template Pack ID',
     181                                'name'                => 'Template Pack Name',
     182                                'version'             => 'Version',
     183                                'wp_required_version' => 'WP required version',
     184                                'bp_required_version' => 'BP required version',
     185                                'description'         => 'Description',
     186                                'author'              => 'Author',
     187                                'link'                => 'Template Pack Link',
     188                                'supports'            => 'Template Pack Supports',
     189                                'text_domain'         => 'Text Domain',
     190                                'domain_path'         => 'Domain Path'
     191                        ) );
     192                }
     193
     194                // The 'buddypress-functions.php' file musts contain headers
     195                if ( empty( $headers ) ) {
     196                        unset( $theme_packages[ $theme_package_id ] );
     197                        continue;
     198                }
     199
     200                // Add the found headers
     201                foreach ( $headers as $tag => $value ) {
     202                        $theme_packages[ $theme_package_id ]->{$tag} = $value;
     203                }
     204
     205                // Finally translate headers
     206                $theme_packages[ $theme_package_id ] = bp_core_admin_translate_theme_package_header( $theme_packages[ $theme_package_id ] );
     207        }
     208
     209        return $theme_packages;
     210}
     211
     212/**
     213 * Displays the Template Packs Table's header & footer.
     214 *
     215 * @since  2.7.0
     216 *
     217 * @return string HTML Output.
     218 */
     219function bp_core_admin_theme_packages_get_head() {
     220        $table_head = array_fill_keys( array( 'head', 'foot' ), array(
     221                'cb'          => '&nbsp;',
     222                'name'        => __( 'Name', 'buddypress' ),
     223                'description' => __( 'Description', 'buddypress' ),
     224                'actions'     => __( 'Actions', 'buddypress' ),
     225        ) );
     226
     227        foreach ( $table_head as $kh => $columns ) {
     228                printf( '<t%1$s>%2$s<tr>%3$s', $kh, "\n\t", "\n" );
     229
     230                foreach ( $columns as $kc => $column ) {
     231                        if ( 'cb' === $kc ) {
     232                                printf(
     233                                        '%1$s<td id="%2$s-%3$s" class="manage-column column-%2$s check-column">%4$s</td>%5$s',
     234                                        "\t\t",
     235                                        $kc,
     236                                        $kh,
     237                                        esc_html( $column ),
     238                                        "\n"
     239                                );
     240                        } else {
     241                                printf(
     242                                        '<th id="%2$s-%3$s" class="manage-column column-%2$s">%4$s</th>%5$s',
     243                                        "\t\t",
     244                                        $kc,
     245                                        $kh,
     246                                        esc_html( $column ),
     247                                        "\n"
     248                                );
     249                        }
     250                }
     251
     252                printf( '%1$s</tr>%2$s<t%3$s>', "\t", "\n", $kh );
     253        }
     254}
     255
     256/**
     257 * Format Template Pack's metas for display.
     258 *
     259 * @since  2.7.0
     260 *
     261 * @param  object $theme_package The Theme Package we need to get metas for.
     262 * @return string HTML Output.
     263 */
     264function bp_core_admin_theme_package_metas( $theme_package = null ) {
     265        $metas = array();
     266
     267        if ( ! empty( $theme_package->version ) ) {
     268                $metas[] = sprintf( _x( 'Version %s', 'Version of the Template Pack', 'buddypress' ), esc_html( $theme_package->version ) );
     269        }
     270
     271        if ( ! empty( $theme_package->author ) ) {
     272                $metas[] = sprintf( _x( 'By %s', 'Name of the author of the Template Pack.', 'buddypress' ), esc_html( $theme_package->author ) );
     273        }
     274
     275        if ( ! empty( $theme_package->link ) ) {
     276                $metas[] = sprintf( '<a href="%1$s">%2$s</a>',
     277                        esc_url( $theme_package->link ),
     278                        __( 'Visit Template Pack site', 'buddypress' )
     279                );
     280        }
     281
     282        if ( empty( $metas ) ) {
     283                return;
     284        }
     285
     286        echo join( ' | ', $metas );
     287}
     288
     289/**
     290 * Sets Template Pack's warnings.
     291 *
     292 * @since  2.7.0
     293 *
     294 * @param  object $theme_package The Theme Package we need to set warnings for.
     295 * @return string HTML Output.
     296 */
     297function bp_core_admin_theme_package_set_warnings( $theme_packages = array() ) {
     298        if ( empty( $theme_packages ) ) {
     299                return $theme_packages;
     300        }
     301
     302        $wp_version        = bp_get_major_wp_version();
     303        $bp_version        = bp_get_version();
     304        $active_components = array_intersect( array_keys( buddypress()->active_components ), bp_core_get_packaged_component_ids() );
     305        $retired_forums    = array_search( 'forums', $active_components );
     306
     307        if ( $retired_forums ) {
     308                $active_components[ $retired_forums ] = 'retired-forums';
     309        }
     310
     311        foreach ( $theme_packages as $ktp => $theme_package ) {
     312                $theme_packages[ $ktp ]->warnings = array();
     313                if ( ! empty( $theme_package->bp_required_version ) && version_compare( $bp_version, $theme_package->bp_required_version, '<' ) ) {
     314                        $theme_packages[ $ktp ]->warnings[] = sprintf( __( 'BuddyPress required version is %s.', 'buddypress' ), $theme_package->bp_required_version );
     315                }
     316
     317                if ( ! empty( $theme_package->wp_required_version ) && version_compare( $wp_version, $theme_package->wp_required_version, '<' ) ) {
     318                        $theme_packages[ $ktp ]->warnings[] = sprintf( __( 'WordPress required version is %s.', 'buddypress' ), $theme_package->wp_required_version );
     319                }
     320
     321                if ( ! empty( $theme_package->supports ) ) {
     322                        $supported_components = array_map( 'trim', explode( ',', $theme_package->supports ) );
     323
     324                        // This component is often forgotten.
     325                        if ( ! array_search( 'members', $supported_components ) ) {
     326                                $supported_components[] = 'members';
     327                        }
     328
     329                        $no_support = array_diff( $active_components, $supported_components );
     330
     331                        if ( ! empty( $no_support ) ) {
     332                                $theme_packages[ $ktp ]->warnings[] = sprintf( __( 'No support is provided for the following component(s): %s.', 'buddypress' ), join( ', ', $no_support ) );
     333                        }
     334                }
     335        }
     336
     337        return $theme_packages;
     338}
     339
     340/**
     341 * Displays the Template Packs Table's body.
     342 *
     343 * @since  2.7.0
     344 *
     345 * @return string HTML Output.
     346 */
     347function bp_core_admin_theme_packages_get_body() {
     348        $theme_packages  = bp_core_admin_get_translated_theme_packages();
     349        $current         = bp_get_theme_package_id();
     350        $theme_packages  = bp_core_admin_theme_package_set_warnings( $theme_packages );
     351        $customizer_args = array(
     352                'url'    => rawurlencode( bp_loggedin_user_domain() ),
     353                'return' => esc_url( add_query_arg( 'page', 'bp-theme-packages', bp_get_admin_url( 'admin.php' ) ) ),
     354        );
     355        ?>
     356
     357        <tbody id="the-list">
     358
     359                <?php if ( empty( $theme_packages ) ) : ?>
     360
     361                        <tr class="no-items">
     362                                <td class="colspanchange" colspan="4"><?php esc_html_e( 'No template packs found.', 'buddypress' ); ?></td>
     363                        </tr>
     364
     365                <?php else : foreach( $theme_packages as $theme_package ) :
     366                        $class           = 'active';
     367                        $activation_link = '';
     368                        $no_compatibity  = ! empty( $theme_package->warnings );
     369
     370                        if ( $theme_package->id !== $current ) {
     371                                $class = 'inactive';
     372                                $activation_link = wp_nonce_url( add_query_arg( array(
     373                                        'page'    => 'bp-theme-packages',
     374                                        'action'  => 'activate',
     375                                        'package' => rawurlencode( $theme_package->dir )
     376                                ), bp_get_admin_url( 'admin.php' ) ), 'bp-theme-packages-activate' );
     377
     378                                $customizer_args['bp_template_pack'] = wp_basename( $theme_package->dir );
     379                        }
     380
     381                        $customizer_link = add_query_arg( $customizer_args, admin_url( 'customize.php' ) );
     382                ?>
     383
     384                        <tr id="<?php echo esc_attr( $theme_package->id ); ?>" class="<?php echo esc_attr( $theme_package->id ) . ' ' . esc_attr( $class ); ?>">
     385                                        <th scope="row" class="check-column">
     386                                                <input type="checkbox" id="bp_theme_package-<?php echo esc_attr( $theme_package->id ); ?>" name="bp_theme_package[packages][]" value="<?php echo esc_attr( $theme_package->dir );?>"<?php echo ( $theme_package->id === 'legacy' || $theme_package->id === $current ) ? 'disabled' : ''; ?> />
     387                                        </th>
     388
     389                                        <td class="plugin-title" style="width: 190px;">
     390                                                <span></span>
     391
     392                                                <label for="bp_theme_package-<?php echo esc_attr( $theme_package->id ); ?>">
     393                                                        <strong><?php echo esc_html( $theme_package->name ); ?></strong>
     394                                                </label>
     395
     396                                                <div class="row-actions-visible"></div>
     397                                        </td>
     398
     399                                        <td class="column-description desc">
     400                                                <div class="plugin-description">
     401                                                        <p><?php echo esc_html( $theme_package->description ); ?></p>
     402                                                </div>
     403
     404                                                <div class="active second plugin-version-author-uri">
     405                                                        <?php bp_core_admin_theme_package_metas( $theme_package ); ?>
     406                                                </div>
     407
     408                                                <?php if ( $no_compatibity ) : ?>
     409
     410                                                        <div class="attention">
     411
     412                                                                <?php echo join( '<br/>', array_map( 'esc_html', $theme_package->warnings ) ); ?>
     413
     414                                                        </div>
     415
     416                                                <?php endif; ?>
     417                                        </td>
     418
     419                                        <td class="column-actions desc">
     420                                                <?php if ( $no_compatibity ) : ?>
     421                                                        <?php esc_html_e( 'Not compatible', 'buddypress' ); ?>
     422                                                <?php elseif ( ! empty( $activation_link ) ) : ?>
     423                                                        <a href="<?php echo esc_url( $activation_link ); ?>" class="button button-primary"><?php esc_html_e( 'Activate', 'buddypress' ); ?></a>
     424                                                        <a href="<?php echo esc_url( $customizer_link ); ?>" class="button button-secondary"><?php esc_html_e( 'Preview', 'buddypress' ); ?></a>
     425                                                <?php else : ?>
     426                                                        <a href="<?php echo esc_url( $customizer_link ); ?>" class="button button-secondary"><?php esc_html_e( 'Customize', 'buddypress' ); ?></a>
     427                                                <?php endif; ?>
     428
     429                                        </td>
     430                                </tr>
     431
     432
     433                <?php endforeach; endif; ?>
     434
     435        </tbody>
     436        <?php
     437}
     438
     439/**
     440 * Requests the filesystem credentials.
     441 *
     442 * NB: if FTP credentials are necessary, we will let Site Owners
     443 * Install or delete Template Packs using their FTP access.
     444 *
     445 * @since  2.7.0
     446 *
     447 * @return bool True if we can manipulate the filesystem. False otherwise.
     448 */
     449function bp_core_admin_theme_packages_has_credentials() {
     450        global $wp_filesystem;
     451
     452        ob_start();
     453        $credentials = request_filesystem_credentials( self_admin_url(), '', false, WP_CONTENT_DIR );
     454        ob_end_clean();
     455
     456        // Try to see if we can have direct access to the filesystem.
     457        if ( false === $credentials || ! WP_Filesystem( $credentials, WP_CONTENT_DIR ) ) {
     458                return false;
     459        }
     460
     461        return true;
     462}
     463
     464/**
     465 * Returns feedback messages to inform the site owner.
     466 *
     467 * @since  2.7.0
     468 *
     469 * @return array The list of feedback messages.
     470 */
     471function bp_core_admin_theme_packages_feedbacks() {
     472        /**
     473         * Filter here to add your own feedback messages.
     474         *
     475         * @since  2.7.0
     476         *
     477         * @param array $value The list of feedback messages.
     478         */
     479        return apply_filters( 'bp_core_admin_theme_packages_feedbacks', array(
     480                'installed'            => __( 'Template pack successfully installed.', 'buddypress' ),
     481                'activated'            => __( 'Template pack successfully activated.', 'buddypress' ),
     482                'deleted'              => __( 'Template pack(s) successfully deleted.', 'buddypress' ),
     483                'missing_archive'      => __( 'The Template Pack zip Archive could not be found.', 'buddypress' ),
     484                'credentials_required' => __( 'We were, not able to access your file system.', 'buddypress' ),
     485                'mkdir_failed'         => __( 'We were, not able to create the "bp-templates" directory in "wp-content".', 'buddypress' ),
     486                'incompatible_archive' => __( 'The Template Pack could not be installed.', 'buddypress' ),
     487                'activation_error'     => __( 'The Template Pack could not be activated.', 'buddypress' ),
     488                'deletion_error'       => __( 'The Template Pack could not be deleted.', 'buddypress' ),
     489                // Will be used in case 'credentials_required', 'mkdir_failed' and 'incompatible_archive'
     490                'how_to'               => __( 'You can use your FTP access to add a "bp-templates" subdirectory to your "wp-content" directory. Once done you will be able to put in it the unzipped Template Pack.', 'buddypress' ) . ' ' .
     491                                          __( 'If you need to delete a Template Pack, make sure to activate another one before using your FTP access to remove its directory.', 'buddypress' ),
     492        ) );
     493}
     494
     495/**
     496 * Manage the actions the site owner requested.
     497 * Possible actions are Upload & Install, delete or activate a Template Pack.
     498 *
     499 * @since  2.7.0
     500 */
     501function bp_core_admin_theme_packages_load() {
     502        // Enqueue the script for the toggle upload support.
     503        wp_enqueue_script( 'plugin-install' );
     504
     505        // Bail if it's not a BP Theme Package form.
     506        if ( empty( $_POST['bp_theme_package'] ) && empty( $_GET['action'] ) ) {
     507                return;
     508        }
     509
     510        // Get the action
     511        $action = bp_admin_list_table_current_bulk_action();
     512
     513        // Build the redirect args.
     514        $query_arg = array( 'page' => 'bp-theme-packages' );
     515
     516        // Handle Template Pack uploads.
     517        if ( 'bp_theme_package_upload' === $action ) {
     518                // Check the nonce.
     519                check_admin_referer( 'bp-theme-packages-upload' );
     520
     521                if ( ! buddypress()->do_autoload ) {
     522                        require dirname( __FILE__ ) . '/classes/class-bp-attachment-theme-package.php';
     523                }
     524
     525                // In multisite, we need to temporarly remove some filters.
     526                if ( is_multisite() ) {
     527                        remove_filter( 'upload_mimes',      'check_upload_mimes'       );
     528                        remove_filter( 'upload_size_limit', 'upload_size_limit_filter' );
     529                }
     530
     531                $zip  = new BP_Attachment_Theme_Package();
     532                $file = $zip->upload( $_FILES );
     533
     534                // Put Multisite filters back.
     535                if ( is_multisite() ) {
     536                        add_filter( 'upload_mimes',      'check_upload_mimes'       );
     537                        add_filter( 'upload_size_limit', 'upload_size_limit_filter' );
     538                }
     539
     540                // Upload errors
     541                if ( ! empty( $file['error'] ) ) {
     542                        buddypress()->admin->errors = $file['error'];
     543                        return;
     544                }
     545
     546                // Extract the Archive
     547                $extracted = $zip->extract();
     548
     549                if ( ! is_wp_error( $extracted ) ) {
     550                        $query_arg['status'] = 'installed';
     551
     552                // File System errors
     553                } else {
     554                        $query_arg['status'] = $extracted->get_error_code();
     555                }
     556
     557        // User chose to delete one or more uploaded Template Pack
     558        } elseif ( 'delete' === $action ) {
     559                global $wp_filesystem;
     560
     561                // Check the nonce.
     562                check_admin_referer( 'bp-theme-packages-delete' );
     563
     564                if ( empty( $_POST['bp_theme_package']['packages'] ) || ! is_array( $_POST['bp_theme_package']['packages'] ) ) {
     565                        $query_arg['status'] = 'deletion_error';
     566                } else {
     567                        if ( ! bp_core_admin_theme_packages_has_credentials() ) {
     568                                $query_arg['status'] = 'credentials_required';
     569                        } else {
     570                                $packages = array_map( 'wp_unslash', $_POST['bp_theme_package']['packages'] );
     571
     572                                foreach( $packages as $package ) {
     573                                        $wp_filesystem->delete( $package, true );
     574                                }
     575
     576                                $query_arg['status'] = 'deleted';
     577                        }
     578                }
     579        } elseif ( 'activate' === $action && ! empty( $_GET['package'] ) ) {
     580                // Check the nonce.
     581                check_admin_referer( 'bp-theme-packages-activate' );
     582
     583                $theme_package_dir  = wp_unslash( $_GET['package'] );
     584                $theme_package_name = wp_basename( $theme_package_dir );
     585
     586                // Bail if we can't activate the Template Pack due to some missing informations.
     587                if ( ! is_dir( $theme_package_dir ) || ! file_exists( $theme_package_dir . '/buddypress-functions.php' ) ) {
     588                        $query_arg['status'] = 'activation_error';
     589
     590                // Process to the activation.
     591                } else {
     592
     593                        // It's legacy we need to delete the Template pack Data.
     594                        if ( 'bp-legacy' === $theme_package_name ) {
     595                                $theme_package_data = array(
     596                                        'id' => 'legacy',
     597                                );
     598
     599                                bp_delete_option( '_bp_theme_package_data' );
     600                                $query_arg['status'] = 'activated';
     601
     602                        // Else try to build the Template Pack data thanks to the buddypress-functions.php's header.
     603                        } else {
     604                                $theme_package_data = get_file_data( $theme_package_dir . '/buddypress-functions.php', array(
     605                                        'id'                  => 'Template Pack ID',
     606                                        'name'                => 'Template Pack Name',
     607                                        'version'             => 'Version',
     608                                        'wp_required_version' => 'WP required version',
     609                                        'bp_required_version' => 'BP required version'
     610                                ) );
     611
     612                                // Bail if we don't have the Theme Package ID
     613                                if ( empty( $theme_package_data['id'] ) ) {
     614                                        $query_arg['status'] = 'activation_error';
     615
     616                                // Find the components support and the Theme Package URL.
     617                                } else {
     618                                        if ( ! empty( $theme_package_data['support'] ) ) {
     619                                                $theme_package_data['support'] = array_map( 'trim', explode( ',', $theme_package_data['support'] ) );
     620                                        }
     621
     622                                        $theme_package_data['dir'] = $theme_package_dir;
     623                                        $locations                 = bp_core_admin_get_theme_packages_locations();
     624                                        $locations_dir             = wp_list_pluck( bp_core_admin_get_theme_packages_locations(), 'dir', 'id' );
     625                                        $url_key                   = array_search( dirname( $theme_package_dir ), $locations_dir );
     626
     627                                        // Bail if we don't have the Theme Package URL
     628                                        if ( empty( $url_key ) ) {
     629                                                $query_arg['status'] = 'activation_error';
     630
     631                                        // Finally update the Theme Package Data option.
     632                                        } else {
     633                                                $theme_package_data['url'] = trailingslashit( $locations[ $url_key ]['url'] ) . $theme_package_name;
     634
     635                                                bp_update_option( '_bp_theme_package_data', $theme_package_data );
     636                                                $query_arg['status'] = 'activated';
     637                                        }
     638                                }
     639                        }
     640
     641                        // Update the Theme Package ID
     642                        bp_update_option( '_bp_theme_package_id', $theme_package_data['id'] );
     643                }
     644
     645        } else {
     646                /**
     647                 * Hook here to run your custom actions
     648                 *
     649                 * @since 2.7.0
     650                 *
     651                 * @param $action the current Template Pack action.
     652                 */
     653                do_action( 'bp_core_admin_theme_packages_load', $action );
     654        }
     655
     656        wp_safe_redirect( add_query_arg( $query_arg, bp_get_admin_url( 'admin.php' ) ) );
     657        exit();
     658}
     659
     660/**
     661 * Main UI to Manage the Template Packs.
     662 *
     663 * @since  2.7.0
     664 *
     665 * @return string HTML Output.
     666 */
     667function bp_core_admin_theme_packages() {
     668        $bp = buddypress();
     669
     670        ?>
     671        <div class="wrap">
     672
     673                <h1><?php _e( 'BuddyPress Settings', 'buddypress' ); ?> </h1>
     674
     675                <h2 class="nav-tab-wrapper"><?php bp_core_admin_tabs( __( 'Template Pack', 'buddypress' ) ); ?></h2>
     676
     677                <?php
     678                // User feedbacks
     679                if ( isset( $_GET['status'] ) || ! empty( $bp->admin->errors ) ) {
     680                        // Defaults to an error.
     681                        $type    = 'error';
     682                        $how_to  = '';
     683
     684                        if ( ! empty( $bp->admin->errors ) ) {
     685                                $feedback = $bp->admin->errors;
     686                        } else {
     687                                $feedbacks = bp_core_admin_theme_packages_feedbacks();
     688
     689                                // Success!
     690                                if ( 'installed' === $_GET['status'] || 'activated' === $_GET['status'] || 'deleted' === $_GET['status'] ) {
     691                                        $type = 'updated';
     692                                }
     693
     694                                if ( 'credentials_required' === $_GET['status'] || 'mkdir_failed' === $_GET['status']
     695                                 || 'incompatible_archive' === $_GET['status'] || 'deletion_error' === $_GET['status'] ) {
     696                                        $how_to = sprintf( '<p class="description">%s</p>', esc_html( $feedbacks['how_to'] ) );
     697                                }
     698
     699                                $feedback = $feedbacks[ $_GET['status'] ];
     700                        }
     701
     702                        // Display the feedback message.
     703                        printf(
     704                                '<div id="message" class="%1$s notice is-dismissible"><p>%2$s</p>%3$s</div>',
     705                                $type,
     706                                esc_html( $feedback ),
     707                                $how_to
     708                        );
     709                }
     710                ;?>
     711
     712                <div class="upload-plugin-wrap">
     713                        <div class="upload-plugin">
     714                                <p class="install-help"><?php esc_html_e( 'If you have a Template Pack in a .zip format, you may install/upgrade it by uploading it here.', 'buddypress' ); ?></p>
     715                                <form method="post" enctype="multipart/form-data" class="wp-upload-form" action="<?php echo esc_url( add_query_arg( array( 'page' => 'bp-theme-packages', 'action' => 'upload' ), bp_get_admin_url( 'admin.php' ) ) ); ?>">
     716                                        <?php wp_nonce_field( 'bp-theme-packages-upload' ); ?>
     717                                        <label class="screen-reader-text" for="pluginzip"><?php esc_html_e( 'Template Pack zip file', 'buddypress' ); ?></label>
     718                                        <input type="file" name="zip" id="zip">
     719                                        <input type="hidden" name="action" id="bp-theme-package-action" value="bp_theme_package_upload" />
     720                                        <input type="submit" name="bp_theme_package[upload]" id="install-plugin-submit" class="button" value="<?php esc_attr_e( 'Install Now', 'buddypress' ); ?>" disabled="">
     721                                </form>
     722                        </div>
     723                </div>
     724
     725                <form action="<?php echo esc_url( add_query_arg( array( 'page' => 'bp-theme-packages' ), bp_get_admin_url( 'admin.php' ) ) ); ?>" method="post" id="bp-core-admin-theme-packages-form">
     726                        <div class="tablenav top">
     727                                <div class="alignleft actions bulkactions">
     728                                        <label for="bulk-action-selector-top" class="screen-reader-text">Select bulk action</label>
     729                                        <select name="action" id="bulk-action-selector-top">
     730                                                <option value="-1"><?php esc_html_e( 'Bulk Actions', 'buddypress' ); ?></option>
     731                                                <option value="delete">Delete</option>
     732                                        </select>
     733                                        <button class="secondary-action button" type="submit" name="bp_theme_package[submit]">
     734                                                <span><?php esc_html_e( 'Apply', 'buddypress' ); ?></span>
     735                                        </button>
     736                                </div>
     737                                <div class="alignleft actions">
     738                                        <button class="secondary-action button upload-view-toggle">
     739                                                <span><?php esc_html_e( 'Upload', 'buddypress' ); ?></span>
     740                                        </button>
     741                                </div>
     742                                <br class="clear">
     743                        </div>
     744
     745                        <table class="widefat plugins" cellspacing="0">
     746
     747                                <?php bp_core_admin_theme_packages_get_head() ;?>
     748
     749                                <?php bp_core_admin_theme_packages_get_body() ;?>
     750
     751                        </table>
     752
     753                        <?php wp_nonce_field( 'bp-theme-packages-delete' ); ?>
     754
     755                </form>
     756        </div>
     757        <?php
     758}
  • src/bp-core/bp-core-theme-compatibility.php

    diff --git src/bp-core/bp-core-theme-compatibility.php src/bp-core/bp-core-theme-compatibility.php
    index ea83317..f282d0d 100644
    function bp_theme_compat_loop_end( $query ) { 
    991991        unset( $bp->theme_compat->is_page_toggled );
    992992}
    993993add_action( 'loop_end', 'bp_theme_compat_loop_end' );
     994
     995/**
     996 * Are we currently previewing a Template Pack in the customizer?
     997 *
     998 * @since  2.7.0
     999 *
     1000 * @return bool|string False if no Template Pack is being previewed.
     1001 *                     The Template Pack folder's name otherwise.
     1002 */
     1003function bp_theme_compat_is_theme_package_preview() {
     1004        $retval = false;
     1005
     1006        if ( empty( $_POST['wp_customize'] ) ) {
     1007                return $retval;
     1008        }
     1009
     1010        $preview_uri = parse_url( bp_get_referer_path() );
     1011
     1012        // Ultimate check.
     1013        if ( false === strpos( $preview_uri['path'], 'customize.php' ) ) {
     1014                return $retval;
     1015        }
     1016
     1017        // Is there a template pack to preview ?
     1018        $vars = wp_parse_args( $preview_uri['query'], array() );
     1019
     1020        $theme_package_key = '';
     1021        if ( ! empty( $vars['bp_template_pack'] ) ) {
     1022                $theme_package_key = sanitize_key( $vars['bp_template_pack'] );
     1023        }
     1024
     1025        // Set the Theme Package Key being previewed
     1026        if ( $theme_package_key ) {
     1027                $retval = $theme_package_key;
     1028        }
     1029
     1030        return $retval;
     1031}
     1032
     1033/**
     1034 * Eventually set the Template Pack ID to the one being previewed
     1035 * into the customizer.
     1036 *
     1037 * @since  2.7.0
     1038 *
     1039 * @param  bool   $retval False.
     1040 * @return string         The theme compat ID.
     1041 */
     1042function bp_theme_compat_set_preview_theme_compat_id( $retval = false ) {
     1043        $bp = buddypress();
     1044
     1045        if ( ! empty( $bp->theme_compat->theme_package_previewed['id'] ) ) {
     1046                $retval = $bp->theme_compat->theme_package_previewed['id'];
     1047        }
     1048
     1049        return $retval;
     1050}
     1051
     1052/**
     1053 * Eventually set the Template Pack Date to the ones being previewed
     1054 * into the customizer.
     1055 *
     1056 * @since  2.7.0
     1057 *
     1058 * @param  string $retval False.
     1059 * @return string         The theme compat Data.
     1060 */
     1061function bp_theme_compat_set_preview_theme_compat_data( $retval = false ) {
     1062        $bp = buddypress();
     1063
     1064        if ( ! empty( $bp->theme_compat->theme_package_previewed ) ) {
     1065                $retval = $bp->theme_compat->theme_package_previewed;
     1066        }
     1067
     1068        return $retval;
     1069}
     1070
     1071/**
     1072 * Set the Theme Package preview for the customizer.
     1073 *
     1074 * @since  2.7.0
     1075 */
     1076function bp_theme_compat_set_theme_package_preview() {
     1077        $theme_package = bp_theme_compat_is_theme_package_preview();
     1078
     1079        // We're not previewing a Template Pack.
     1080        if ( ! $theme_package ) {
     1081                return;
     1082        }
     1083
     1084        // Now we are sure to preview a Template pack, load the Admin file.
     1085        require_once( trailingslashit( buddypress()->plugin_dir ) . 'bp-core/admin/bp-core-admin-theme-packages.php' );
     1086
     1087        // Get all available Theme packages
     1088        $theme_packages = bp_core_admin_get_translated_theme_packages();
     1089
     1090        // If we're not finding the Theme Package stop!
     1091        if ( empty( $theme_packages[ $theme_package ] ) ) {
     1092                return;
     1093        }
     1094
     1095        // Set the global to use in filters
     1096        buddypress()->theme_compat->theme_package_previewed = array(
     1097                'id'      => $theme_packages[ $theme_package ]->id,
     1098                'name'    => $theme_packages[ $theme_package ]->name,
     1099                'version' => $theme_packages[ $theme_package ]->version,
     1100                'dir'     => $theme_packages[ $theme_package ]->dir,
     1101                'url'     => $theme_packages[ $theme_package ]->url,
     1102        );
     1103
     1104        // Prefilter options used to load the Template Pack
     1105        add_filter( 'pre_option__bp_theme_package_id', 'bp_theme_compat_set_preview_theme_compat_id', 10, 1 );
     1106
     1107        if ( 'bp-legacy' !== $theme_package ) {
     1108                add_filter( 'pre_option__bp_theme_package_data', 'bp_theme_compat_set_preview_theme_compat_data' );
     1109        }
     1110}
     1111add_action( 'bp_register_theme_packages', 'bp_theme_compat_set_theme_package_preview', 5 );
  • src/bp-core/classes/class-bp-admin.php

    diff --git src/bp-core/classes/class-bp-admin.php src/bp-core/classes/class-bp-admin.php
    index 5946130..6e0b604 100644
    class BP_Admin { 
    121121                        require( $this->admin_dir . 'bp-core-admin-classes.php'    );
    122122                }
    123123
    124                 require( $this->admin_dir . 'bp-core-admin-actions.php'    );
    125                 require( $this->admin_dir . 'bp-core-admin-settings.php'   );
    126                 require( $this->admin_dir . 'bp-core-admin-functions.php'  );
    127                 require( $this->admin_dir . 'bp-core-admin-components.php' );
    128                 require( $this->admin_dir . 'bp-core-admin-slugs.php'      );
    129                 require( $this->admin_dir . 'bp-core-admin-tools.php'      );
     124                require( $this->admin_dir . 'bp-core-admin-actions.php'        );
     125                require( $this->admin_dir . 'bp-core-admin-settings.php'       );
     126                require( $this->admin_dir . 'bp-core-admin-functions.php'      );
     127                require( $this->admin_dir . 'bp-core-admin-components.php'     );
     128                require( $this->admin_dir . 'bp-core-admin-slugs.php'          );
     129                require( $this->admin_dir . 'bp-core-admin-tools.php'          );
     130                require( $this->admin_dir . 'bp-core-admin-theme-packages.php' );
    130131        }
    131132
    132133        /**
    class BP_Admin { 
    270271                        'bp_core_admin_settings'
    271272                );
    272273
     274                $hooks[] = add_submenu_page(
     275                        $this->settings_page,
     276                        __( 'BuddyPress Template Pack', 'buddypress' ),
     277                        __( 'BuddyPress Template Pack', 'buddypress' ),
     278                        $this->capability,
     279                        'bp-theme-packages',
     280                        'bp_core_admin_theme_packages'
     281                );
     282
    273283                // For consistency with non-Multisite, we add a Tools menu in
    274284                // the Network Admin as a home for our Tools panel.
    275285                if ( is_multisite() && bp_core_do_network_admin() ) {
    class BP_Admin { 
    327337
    328338                foreach( $hooks as $hook ) {
    329339                        add_action( "admin_head-$hook", 'bp_core_modify_admin_menu_highlight' );
     340
     341                        if ( false !== strpos( $hook, 'bp-theme-packages' ) ) {
     342                                add_action( "load-{$hook}", 'bp_core_admin_theme_packages_load' );
     343                        }
    330344                }
    331345        }
    332346
    class BP_Admin { 
    524538        public function admin_head() {
    525539
    526540                // Settings pages.
    527                 remove_submenu_page( $this->settings_page, 'bp-page-settings' );
    528                 remove_submenu_page( $this->settings_page, 'bp-settings'      );
     541                remove_submenu_page( $this->settings_page, 'bp-page-settings'  );
     542                remove_submenu_page( $this->settings_page, 'bp-settings'       );
     543                remove_submenu_page( $this->settings_page, 'bp-theme-packages' );
    529544
    530545                // Network Admin Tools.
    531546                remove_submenu_page( 'network-tools', 'network-tools' );
  • src/bp-core/classes/class-bp-attachment-theme-package.php

    diff --git src/bp-core/classes/class-bp-attachment-theme-package.php src/bp-core/classes/class-bp-attachment-theme-package.php
    index e69de29..02c73af 100644
     
     1<?php
     2/**
     3 * Core Theme Package attachment class.
     4 *
     5 * @package BuddyPress
     6 * @subpackage Core
     7 * @since 2.7.0
     8 */
     9
     10// Exit if accessed directly.
     11defined( 'ABSPATH' ) || exit;
     12
     13/**
     14 * BP Attachment Theme Package class.
     15 *
     16 * Extends BP Attachment to manage the Theme Package uploads.
     17 *
     18 * @since 2.7.0
     19 */
     20class BP_Attachment_Theme_Package extends BP_Attachment {
     21        /**
     22         * The constuctor.
     23         *
     24         * @since 2.7.0
     25         */
     26        public function __construct() {
     27                // Allowed theme package types & upload size.
     28                $allowed_types        = array( 'zip' );
     29                $max_upload_file_size = bp_attachments_get_max_upload_file_size( 'theme_package' );
     30
     31                parent::__construct( array(
     32                        'action'                => 'bp_theme_package_upload',
     33                        'file_input'            => 'zip',
     34                        'original_max_filesize' => $max_upload_file_size,
     35                        'base_dir'              => bp_attachments_uploads_dir_get( 'dir' ),
     36                        'allowed_mime_types'    => $allowed_types,
     37
     38                        // Specific errors for cover images.
     39                        'upload_error_strings'  => array(
     40                                11  => sprintf( __( 'That zip archive is too big. Please upload one smaller than %s', 'buddypress' ), size_format( $max_upload_file_size ) ),
     41                                12  => sprintf( __( 'Please upload only this file type: %s.', 'buddypress' ), reset( $allowed_types ) ),
     42                        ),
     43                ) );
     44        }
     45
     46        /**
     47         * Theme Package specific rules.
     48         *
     49         * @since 2.7.0
     50         *
     51         * @param array $file The temporary file attributes (before it has been moved).
     52         * @return array $file The file with extra errors if needed.
     53         */
     54        public function validate_upload( $file = array() ) {
     55                // Bail if already an error.
     56                if ( ! empty( $file['error'] ) ) {
     57                        return $file;
     58                }
     59
     60                // File size is too big.
     61                if ( $file['size'] > $this->original_max_filesize ) {
     62                        $file['error'] = 11;
     63
     64                // File is of invalid type.
     65                } elseif ( ! bp_attachments_check_filetype( $file['tmp_name'], $file['name'], array( 'zip' => 'application/zip' ) ) ) {
     66                        $file['error'] = 12;
     67                }
     68
     69                // Return with error code attached.
     70                return $file;
     71        }
     72
     73        /**
     74         * Set the directory when uploading a file.
     75         *
     76         * @since 2.7.0
     77         *
     78         * @param array $upload_dir The original Uploads dir.
     79         * @return array $value Upload data (path, url, basedir...).
     80         */
     81        public function upload_dir_filter( $upload_dir = array() ) {
     82
     83                // Set the subdir.
     84                $subdir  = '/bp-templates';
     85
     86                /**
     87                 * Filters the Theme Package upload directory.
     88                 *
     89                 * @since 2.7.0
     90                 *
     91                 * @param array $value      Array containing the path, URL, and other helpful settings.
     92                 * @param array $upload_dir The original Uploads dir.
     93                 */
     94                return apply_filters( 'bp_attachments_theme_package_upload_dir', array(
     95                        'path'    => $this->upload_path . $subdir,
     96                        'url'     => $this->url . $subdir,
     97                        'subdir'  => $subdir,
     98                        'basedir' => $this->upload_path,
     99                        'baseurl' => $this->url,
     100                        'error'   => false
     101                ), $upload_dir );
     102        }
     103
     104        /**
     105         * Extract the content of the Tempate Pack archive to
     106         * /wp-content/bp-templates.
     107         *
     108         * @since  2.7.0
     109         *
     110         * @param  string $zip Absolute path to the uploaded zip archive.
     111         * @return WP_Error|string      An error if something went wrong during the process.
     112         *                              Abolute path to the Template Pack dir otherwise.
     113         */
     114        public function extract( $zip = '' ) {
     115                global $wp_filesystem;
     116
     117                if ( empty( $zip ) && ! empty( $this->attachment['file'] ) ) {
     118                        $zip = $this->attachment['file'];
     119                }
     120
     121                if ( ! file_exists( $zip ) ) {
     122                        return new WP_Error( 'missing_archive' );
     123                }
     124
     125                if ( ! bp_core_admin_theme_packages_has_credentials() ) {
     126                        // Unlink the zip from the uploads.
     127                        unlink( $zip );
     128
     129                        return new WP_Error( 'credentials_required' );
     130                }
     131
     132                // The Theme Packages destination is at the root of /wp-content
     133                $theme_packages_destination = WP_CONTENT_DIR . '/bp-templates';
     134
     135                // Create the BP Templates Dir if it doesn't exit already.
     136                if ( ! $wp_filesystem->exists( $theme_packages_destination ) ) {
     137                        if ( ! $wp_filesystem->mkdir( $theme_packages_destination, FS_CHMOD_DIR ) ) {
     138                                return new WP_Error( 'mkdir_failed' );
     139                        }
     140                }
     141
     142                // Set the Theme Package directory
     143                $theme_package_dir = $theme_packages_destination . '/' . basename( basename( $zip, '.tmp' ), '.zip' );
     144
     145                // Clean up any existing Theme Package.
     146                if ( $wp_filesystem->is_dir( $theme_package_dir ) ) {
     147                        $wp_filesystem->delete( $theme_package_dir, true );
     148                }
     149
     150                // Extract the Theme Package
     151                $result = unzip_file( $zip, $theme_packages_destination );
     152
     153                // Once extracted, delete the zip.
     154                unlink( $zip );
     155
     156                if ( is_wp_error( $result ) ) {
     157
     158                        $wp_filesystem->delete( $theme_package_dir, true );
     159                        if ( 'incompatible_archive' === $result->get_error_code() ) {
     160                                return new WP_Error( 'incompatible_archive' );
     161                        }
     162                        return $result;
     163                }
     164
     165                return $theme_package_dir;
     166        }
     167}
  • src/bp-loader.php

    diff --git src/bp-loader.php src/bp-loader.php
    index 1d05b96..94df8a3 100644
    class BuddyPress { 
    560560                        'BP_Admin'                     => 'core',
    561561                        'BP_Attachment_Avatar'         => 'core',
    562562                        'BP_Attachment_Cover_Image'    => 'core',
     563                        'BP_Attachment_Theme_Package'  => 'core',
    563564                        'BP_Attachment'                => 'core',
    564565                        'BP_Button'                    => 'core',
    565566                        'BP_Component'                 => 'core',
    class BuddyPress { 
    756757                        'url'     => trailingslashit( $this->themes_url . '/bp-legacy' )
    757758                ) );
    758759
     760                if ( 'legacy' !== bp_get_theme_package_id() ) {
     761                        $theme_package_data = bp_get_option( '_bp_theme_package_data', array() );
     762
     763                        // Check the Theme Package dir is set and exits before registering it.
     764                        if ( ! empty( $theme_package_data['dir'] ) && is_dir( $theme_package_data['dir'] ) ) {
     765                                bp_register_theme_package( $theme_package_data );
     766                        }
     767                }
     768
    759769                // Register the basic theme stack. This is really dope.
    760770                bp_register_template_stack( 'get_stylesheet_directory', 10 );
    761771                bp_register_template_stack( 'get_template_directory',   12 );