Articles > FluentCommunity Helper for Astra

FluentCommunity Helper for Astra

getting geeky with the code

Posted in:

FluentCommunity lives in a halfway realm; it’s in WordPress, but it’s not. On a default install, your community, or “portal”, will look very different to the rest of your website. It doesn’t use your theme at all.

There are good reasons for this – mostly around performance – and once you get your head around it, you can live with it. There is also a theme compatibility feature that makes it possible to present your usual pages within the portal layout. You can even use hooks to make your posts and CPTs appear within a portal layout.

With some thought, and a substantial dollop of CSS, you can produce a pretty seamless experience between your portal and the rest of the site.

But it’s not perfect

The problem is, theme compatibility bypasses lots of the usual hooks and layouts that your theme provides. So while your page builder of choice (mine is Beaver Builder) will probably work nicely on pages, as soon as you start using layouts for post types things get ignored.

For example, if I set a CPT to use theme compatibility, I can use Beaver Themer to create layouts that are inserted before or after content, but not actually replace content.

The same goes for post types created by some plugins. I was trying out Tutor LMS (for clients who need more powerful LMS functionality that FluentCommunity offers, but still want an easy-to-use interface and not spend a fortune) – none of the course or lesson content appears when the CPTs are set to use theme compatibility.

My Plan

What if I could hook into my current theme and insert the necessary actions for the portal header, footer and sidebar (whilst disabling the default header and footer)?

So I’ve put this together for my theme of choice – Astra – and it appears to work. Beaver Themer (the layout building side of Beaver Builder) and Tutor LMS both behave how they should.

It is important to understand that I do not sit here writing WordPress plugins day-in and day-out. I build websites, not code! But between my acquaintances (not quite ready to call them friends), ChatGPT, Gemini, and me, I came up with something that works. Be gentle.

I would love someone to do a better job. If Fluent Community had this in core, I know it would make a lot of people happy. Or if someone wants to take it, make it better and then maintain it, feel free. I just hope I get a free copy!

“My” Plugin

I’ve listed all the component files here, but there is also a zipfile available for download. If you want to use it, or the code below, you do so entirely at your own risk.

First is just the bootstrap that calls all the other components into action. In my live version, I also have some extra code that goes in here to enable automatic updates – but that’s not a service I’m offering to the world!

fm-fc-helper-astra/fm-fc-helper-astra.php

<?php
/**
 * Plugin Name: fm FC Helper for Astra
 * Description: Integrates Fluent Community wrappers into Astra by replacing headers/footers based on post type and page selection.
 * Version: 1
 * Author: fairlymarvellous.co.uk
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// Define the plugin directory path for easy template loading
define( 'FM_FC_HELPER_DIR', plugin_dir_path( __FILE__ ) );

// Include our separated logic classes
require_once FM_FC_HELPER_DIR . 'includes/class-fm-fc-admin.php';
require_once FM_FC_HELPER_DIR . 'includes/class-fm-fc-frontend.php';

// Initialize Admin logic only if in the backend
if ( is_admin() ) {
    new FM_FC_Admin();
}

// Always initialize Frontend (it does its own checks inside `init_plugin_logic`)
new FM_FC_Frontend();

I wanted to make it simple to select post types (and their associated archives) as well as individual pages that should be pushed through Fluent Community by the plugin. This adds a settings screen and a meta box in pages.

fm-fc-helper-astra/includes/class-fm-fc-admin.php

<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class FM_FC_Admin {

    public function __construct() {
        add_action( 'admin_menu', [ $this, 'add_settings_page' ] );
        add_action( 'admin_init', [ $this, 'register_settings' ] );

        add_action( 'add_meta_boxes', [ $this, 'add_page_metabox' ] );
        add_action( 'save_post_page', [ $this, 'save_page_meta' ] );

        add_filter( 'manage_page_posts_columns', [ $this, 'add_column_head' ] );
        add_action( 'manage_page_posts_custom_column', [ $this, 'render_column_content' ], 10, 2 );
    }

    public function add_settings_page() {
        add_options_page(
            'fm FC Helper Settings',
            'fm FC Helper',
            'manage_options',
            'fm-fc-helper',
            [ $this, 'render_settings' ]
        );
    }

    public function register_settings() {
        register_setting(
            'fm_fc_helper_group',
            'fm_fc_post_types',
            [ 'sanitize_callback' => [ $this, 'sanitize_post_types' ] ]
        );
    }

    public function sanitize_post_types( $input ) {
        if ( is_array( $input ) ) {
            return array_map( 'sanitize_key', $input );
        }
        return [];
    }

    public function render_settings() {
        if ( ! current_user_can( 'manage_options' ) ) return;

        $selected_types = (array) get_option( 'fm_fc_post_types', [] );
        $post_types = get_post_types( [ 'public' => true ], 'objects' );
        ?>
        <div class="wrap">
            <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
            <form method="post" action="options.php">
                <?php settings_fields( 'fm_fc_helper_group' ); ?>
                <h2>Select Post Types</h2>
                <table class="form-table">
                <?php foreach ( $post_types as $type ) : ?>
                    <tr>
                        <th scope="row"><?php echo esc_html( $type->labels->name ); ?></th>
                        <td>
                            <input type="checkbox" name="fm_fc_post_types[]" value="<?php echo esc_attr( $type->name ); ?>" <?php checked( in_array( $type->name, $selected_types, true ) ); ?>>
                        </td>
                    </tr>
                <?php endforeach; ?>
                </table>
                <?php submit_button(); ?>
            </form>
        </div>
        <?php
    }

    public function add_page_metabox() {
        add_meta_box(
            'fm_fc_meta',
            'fm FC Helper',
            [ $this, 'render_metabox' ],
            'page',
            'side'
        );
    }

    public function render_metabox( $post ) {
        wp_nonce_field( 'fm_fc_meta_nonce_action', 'fm_fc_meta_nonce' );
        $value = get_post_meta( $post->ID, '_fm_fc_active', true );
        ?>
        <label>
            <input type="checkbox" name="fm_fc_active" value="1" <?php checked( $value, '1' ); ?>>
            Activate FC Helper on this page
        </label>
        <?php
    }

    public function save_page_meta( $post_id ) {
        if ( ! isset( $_POST['fm_fc_meta_nonce'] ) || ! wp_verify_nonce( $_POST['fm_fc_meta_nonce'], 'fm_fc_meta_nonce_action' ) ) return;
        if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
        if ( ! current_user_can( 'edit_page', $post_id ) ) return;

        if ( isset( $_POST['fm_fc_active'] ) ) {
            update_post_meta( $post_id, '_fm_fc_active', '1' );
        } else {
            delete_post_meta( $post_id, '_fm_fc_active' );
        }
    }

    public function add_column_head( $columns ) {
        $columns['fm_fc_status'] = 'FC Active';
        return $columns;
    }

    public function render_column_content( $column, $post_id ) {
        if ( $column !== 'fm_fc_status' ) return;

        if ( get_post_meta( $post_id, '_fm_fc_active', true ) ) {
            echo '<span style="color:#ff6600;font-weight:bold;">[Active]</span>';
        }
    }
}

Next is the part that checks whether the current post or page on the frontend should be pushed through the community, and uses an Astra hook to push in the FC header and then just pushes the FC footer through a general WP hook.

It also disables the Astra header and footer, and checks that the Beaver Builder (my tool of choice) editor isn’t open.

fm-fc-helper-astra/includes/class-fm-fc-frontend.php

<?php

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

class FM_FC_Frontend {

    private $should_activate = null;

    public function __construct() {
        add_action( 'wp', [ $this, 'init_plugin_logic' ] );
    }

    public function init_plugin_logic() {
        if ( is_admin() ) return;
        if ( ! defined( 'ASTRA_THEME_VERSION' ) ) return;
        if ( class_exists( 'FLBuilderModel' ) && \FLBuilderModel::is_builder_active() ) return;
        if ( ! $this->should_activate() ) return;

        remove_action( 'astra_header', 'astra_header_markup' );

        if ( class_exists( 'Astra_Builder_Footer' ) ) {
            remove_action( 'astra_footer', [ Astra_Builder_Footer::get_instance(), 'footer_markup' ] );
        }

        add_action( 'astra_header_after', [ $this, 'load_header_template' ] );
        add_action( 'wp_footer', [ $this, 'load_footer_template' ], 5 );
        add_filter( 'body_class', [ $this, 'add_body_class' ] );
    }

    private function should_activate() {
        if ( $this->should_activate !== null ) {
            return $this->should_activate;
        }

        $this->should_activate = false;
        $selected_types = (array) get_option( 'fm_fc_post_types', [] );
        $queried_id = get_queried_object_id();

        if ( is_page() && $queried_id ) {
            if ( get_post_meta( $queried_id, '_fm_fc_active', true ) === '1' ) {
                $this->should_activate = true;
                return true;
            }
        }

        foreach ( $selected_types as $type ) {
            if ( is_singular( $type ) || is_post_type_archive( $type ) ) {
                $this->should_activate = true;
                return true;
            }

            if ( $type === 'post' && ( is_home() || is_category() || is_tag() ) ) {
                $this->should_activate = true;
                return true;
            }
        }

        return false;
    }

    public function add_body_class( $classes ) {
        $classes[] = 'fluent_com_wp_pages';
        return $classes;
    }

    private function get_template_path( $template ) {
        // Uses the constant defined in the main plugin file to ensure the path is correct
        return FM_FC_HELPER_DIR . 'templates/' . $template;
    }

    public function load_header_template() {
        include $this->get_template_path( 'header.php' );
    }

    public function load_footer_template() {
        include $this->get_template_path( 'footer.php' );
    }
}

Finally, the code we inject to load the FC header…

fm-fc-helper-astra/templates/header.php

<?php
if ( ! defined( 'ABSPATH' ) ) exit;

do_action( 'fluent_community/enqueue_global_assets', true );
do_action( 'fluent_community/before_portal_dom' );
?>

<style id="fcom-chrome-layout">

html { font-size:100%; }

.feed_layout {
    --global-vw: var(--fcom-main-content-width,100vw);
}

@media (min-width:1024px) {

.feeds_main.fcom_wp_content {
padding:0!important;
}

}

[data-vertical-spacing*="top"] {
padding-top:0!important;
}

.hero-section[data-type=type-1] {
margin-bottom:0!important;
}

</style>

<div class="fcom_wrap fcom_wp_frame">
<div class="fluent_com">
<div class="fhr_wrap">

<?php do_action( 'fluent_community/portal_header', 'wp' ); ?>

<div class="fhr_content">
<div class="fhr_home">

<div class="feed_layout">
<div class="spaces">

<div id="fluent_community_sidebar_menu" class="space_contents">

<?php do_action( 'fluent_community/portal_sidebar', 'wp' ); ?>

</div>

</div>

<div class="feeds_main fcom_wp_content fcom_fallback_wp_content">

…and FC footer…

fm-fc-helper-astra/templates/footer.php

<?php
if ( ! defined( 'ABSPATH' ) ) exit;
?>

</div>
</div>
</div>
</div>
</div>
</div>
</div>

<?php do_action( 'fluent_community/template_footer' ); ?>

Download, if you dare…

This plugin comes with absolutely no support whatsoever… downloading it and/or installing it is done entirely at your own risk!

FacebookBlueskyLinkedInEmail
getting geeky with the code

Posted in:

Recent Articles