File: /home/nhathuocat/public_html/wp-content/plugins/fixed-toc/frontend/class-frontend-control.php
<?php
/**
* Control the whole frontend.
*
* @since 3.0.0
*/
class Fixedtoc_Frontend_Control {
/**
* a data for creating TOC
*
* @since 3.0.0
* @access private
*
* @var array
*/
private $data = array();
/**
* The raw post content.
*
* @since 3.1.0
* @var string
*/
private $raw_post_content = '';
/**
* Whether multi-page.
*
* @since 3.1.0
*
* @var bool
*/
private $multipage = false;
/**
* Constructor.
*
* @since 3.1.22 Add the filter 'fixedtoc_frontend_init_hook'
* @since 3.0.0
*/
public function __construct() {
add_action(
apply_filters( 'fixedtoc_frontend_init_hook', 'wp_enqueue_scripts' ),
array( $this, 'create_data' ),
apply_filters( 'fixedtoc_frontend_init_priority', 11 )
);
}
/**
* Create data
* Continues to create TOC if data not empty
*
* @since 3.0.0
*
* @noinspection PhpUndefinedClassInspection
*/
public function create_data() {
// Check if is TOC page.
if ( ! fixedtoc_is_true( 'toc_page' ) ) {
return;
}
global $post, $pages, $multipage;
// Check if the $post is defined.
if ( ! isset( $post ) ) {
return;
}
// Get post content.
$is_postdata = setup_postdata( $post );
if ( ! $is_postdata ) {
return;
}
$this->raw_post_content = get_the_content();
$this->multipage = (bool) $multipage;
// Create data
do_action( 'fixedtoc_before_creating_data' );
require_once 'data/class-data.php';
// Compatible with Disqus Conditional Load
/** @noinspection PhpUndefinedConstantInspection */
include_once( ABSPATH . 'wp-admin/includes/plugin.php' );
$is_disqus_conditional_load_activated = is_plugin_active( 'disqus-conditional-load/disqus-conditional-load.php' );
if ( $is_disqus_conditional_load_activated ) {
remove_shortcode( 'js-disqus' );
remove_shortcode( 'dcl-comments' );
}
// Compatible with Disqus Conditional Load
if ( $this->is_multipage() ) {
$this->data = $this->create_multipage_data( $pages );
} else {
// Support the Beaver Builder plugin
if ( method_exists( 'FLBuilderModel', 'is_builder_enabled' ) && FLBuilderModel::is_builder_enabled() && method_exists( 'FLBuilder', 'render_content_by_id' ) ) {
ob_start();
FLBuilder::render_content_by_id( $post->ID );
$single_content = ob_get_clean();
} else {
$single_content = $this->raw_post_content;
}
$this->data = $this->create_single_data( $single_content );
}
// Compatible with Disqus Conditional Load
if ( $is_disqus_conditional_load_activated ) {
add_shortcode( 'js-disqus', array( Disqus_Conditional_Load::$instance->public, 'comment_shortcode' ) );
add_shortcode( 'dcl-comments', array(
Disqus_Conditional_Load::$instance->public,
'comment_shortcode'
) );
}
// Compatible with Disqus Conditional Load
wp_reset_postdata();
// Check if is empty data.
if ( empty( $this->data ) ) {
return;
}
// Check if is larger than min heading num
if ( count( $this->data ) < fixedtoc_get_val( 'general_min_headings_num' ) ) {
return;
}
// Continue to create TOC if data not empty.
$GLOBALS['FTOC_HAS_DATA'] = true;
// Defines Debug mode.
define( 'FTOC_DEBUG', fixedtoc_get_option( 'developer_debug' ) );
// Create TOC
$this->create_toc();
}
/**
* Detect if multi page.
*
* @since 3.1.0
*
* @return bool
*/
private function is_multipage() {
return $this->multipage;
}
/**
* Create data from single page content.
*
* @since 3.1.0
*
* @param string $content
*
* @return array|array[]
*/
private function create_single_data( $content ) {
$content = $this->filter_input_raw_content( $content );
$content = apply_filters( 'the_content', $content );
$content = $this->filter_input_content( $content );
$obj_data = new Fixedtoc_Data( $content );
if ( $obj_data->has_matches() ) {
// Load datum files here
require_once 'data/abstract-datum.php';
require_once 'data/class-datum-element.php';
require_once 'data/class-datum-id.php';
require_once 'data/class-datum-title.php';
require_once 'data/class-datum-origin-title.php';
require_once 'data/class-datum-parent-id.php';
// Add datum object here
$datum = array(
new Fixedtoc_Datum_Origin_Title(),
new Fixedtoc_Datum_Title(),
new Fixedtoc_Datum_element(),
new Fixedtoc_Datum_Id(),
new Fixedtoc_Datum_Parent_Id()
);
$obj_data->create_data( $datum );
return $obj_data->get_data();
}
return array();
}
/**
* Create a data from multipage content.
*
* @since 3.1.0
*
* @param string $multipage_contents
*
* @return array|array[]
*/
private function create_multipage_data( $multipage_contents ) {
if ( empty( $multipage_contents ) || ! is_array( $multipage_contents ) ) {
return array();
}
$i = 1;
$multipage_content = '';
$multipage_filter_content = '';
foreach ( $multipage_contents as $content ) {
$content = $this->filter_input_raw_content( $content );
$content = apply_filters( 'the_content', $content );
$multipage_content .= $content;
$multipage_filter_content .= preg_replace( '/(<h\d)(.*?>.+?<\/h\d>)/i', '${1}' . ' data-page="' . $i . '"${2}', $content );
$i ++;
}
if ( empty( $multipage_content ) || empty( $multipage_filter_content ) ) {
return array();
}
// Combine $data;
$multipage_content = $this->filter_input_content( $multipage_content );
$data = $this->create_single_data( $multipage_content );
if ( $data ) {
// Create data include page data.
$obj_data = new Fixedtoc_Data( $multipage_filter_content );
require_once 'data/class-datum-page.php';
$obj_datum = array( new Fixedtoc_Datum_page() );
$obj_data->create_data( $obj_datum );
$page_data = $obj_data->get_data();
// Merger
$j = 0;
$new_data = array();
foreach ( $data as $datum ) {
$new_data[] = array_merge( $datum, $page_data[ $j ] );
$j ++;
}
return $new_data;
}
return array();
}
/**
* Add necessary hooks to create TOC.
*
* @since 3.0.0
*
* @return void
*/
private function create_toc() {
require_once 'html/abstract-element.php';
require_once 'html/class-dom.php';
if ( ! fixedtoc_is_true( 'in_widget' ) || fixedtoc_amp_is_request() ) {
add_filter( 'the_content', array(
$this,
'create_html'
), apply_filters( 'fixedtoc_create_html_priority', 20 ) );
}
add_filter( 'the_content', array(
$this,
'modify_post_headings'
), apply_filters( 'fixedtoc_filter_headings_priority', 99999 ) );
add_filter( 'the_content', array(
$this,
'wrap_postcontent'
), apply_filters( 'fixedtoc_wrap_content_priority', 11 ) );
add_filter( 'body_class', array( $this, 'add_body_class' ) );
add_filter( 'post_class', array( $this, 'add_post_class' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ), 20 );
add_action( 'wp_footer', array( $this, 'localize_scripts' ) );
add_action( 'wp_footer', array( $this, 'hacks' ) );
if ( fixedtoc_is_true( 'in_widget' ) && ! fixedtoc_amp_is_request() ) {
add_filter( 'fixedtoc_widget_content', array( $this, 'create_html_for_widget' ) );
}
if ( fixedtoc_is_true( 'in_post' ) ) {
$this->toc_shortcode();
}
}
/**
* Create HTML code of TOC.
*
* @since 3.0.0
*
* @param string $content
* The post content.
*
* @return string
*/
public function create_html( $content ) {
if ( ! $this->verify_content_context() ) {
return $content;
}
if ( has_shortcode( $this->raw_post_content, 'toc' ) ) {
return $content;
}
$in_post = fixedtoc_is_true( 'in_post' );
if ( $in_post || fixedtoc_amp_is_request() ) {
require_once 'html/class-element-container-outer.php';
$obj_dom = new Fixedtoc_Dom( new Fixedtoc_Element_Container_outer( $this->data ) );
} else {
require_once 'html/class-element-container.php';
$obj_dom = new Fixedtoc_Dom( new Fixedtoc_Element_Container( $this->data ) );
}
$position_in_post = fixedtoc_get_val( 'contents_position_in_post' );
$toc = $obj_dom->get_html();
if ( $in_post && 'top' != $position_in_post ) {
$preg = '/(\<h\d)(.*?>.+?\<\/h\d\>)/i';
$placeholder = '%FIXEDTOC_PLACEHOLDER%';
switch ( $position_in_post ) {
case 'before_1st_heading' :
{
$content = preg_replace( $preg, $placeholder . '${0}', $content, 1 );
$content = str_replace( $placeholder, $toc, $content );
break;
}
case 'after_1st_heading' :
{
$content = preg_replace( $preg, '${0}' . $placeholder, $content, 1 );
$content = str_replace( $placeholder, $toc, $content );
break;
}
case 'before_2nd_heading':
{
$content = preg_replace( $preg, $placeholder . '${0}', $content, 2 );
$content = preg_replace( '/' . $placeholder . '/', '', $content, 1 );
$content = str_replace( $placeholder, $toc, $content );
break;
}
case 'after_2nd_heading':
{
$content = preg_replace( $preg, '${0}' . $placeholder, $content, 2 );
$content = preg_replace( '/' . $placeholder . '/', '', $content, 1 );
$content = str_replace( $placeholder, $toc, $content );
break;
}
default:
{
$content = $toc . "\n" . $content;
}
}
} else {
$content = $toc . "\n" . $content;
}
return $content;
}
/**
* Create HTML code of TOC for the widget.
*
* @since 3.1.14
*
* @param string $content
*
* @return string
*/
public function create_html_for_widget( $content ) {
require_once 'html/class-element-container.php';
$obj_dom = new Fixedtoc_Dom( new Fixedtoc_Element_Container( $this->data ) );
return $obj_dom->get_html() . "\n" . $content;
}
/**
* Add extra classes to body.
*
* @since 3.0.0
*
* @param array $classes .
*
* @return array
*/
public function add_body_class( $classes ) {
$classes[] = 'has-ftoc';
return $classes;
}
/**
* Add extra classes to post.
*
* @since 3.0.0
*
* @param array $classes .
*
* @return array
*/
public function add_post_class( $classes ) {
$classes[] = 'post-ftoc';
// if ( fixedtoc_is_true( 'in_post' ) ) {
// $classes[] = 'ftwp-in-post';
// }
return $classes;
}
/**
* Add styles and Javascript.
*
* @since 3.0.0
*
* @return void
*/
public function enqueue_scripts() {
// Enqueue css
if ( FTOC_DEBUG ) {
wp_enqueue_style( 'fixedtoc-style', plugins_url( 'assets/css/ftoc.css', __FILE__ ), array(), time() );
} else {
wp_enqueue_style( 'fixedtoc-style', plugins_url( 'assets/css/ftoc.min.css', __FILE__ ), array(), FTOC_VERSION );
}
// Inline css
if ( fixedtoc_is_true( 'in_widget' ) ) {
add_action( 'fixedtoc_before_widget', array( $this, 'add_inline_style' ) );
} else {
$this->add_inline_style();
}
// Custom css
$custom_css = fixedtoc_get_val( 'general_css' );
if ( $custom_css ) {
wp_add_inline_style( 'fixedtoc-style', wp_strip_all_tags( $custom_css, true ) );
}
// Enqueue JS
if ( fixedtoc_amp_is_request() ) {
return;
}
if ( FTOC_DEBUG ) {
wp_enqueue_script( 'fixedtoc-js', plugins_url( 'assets/js/ftoc.js', __FILE__ ), array( 'jquery' ), time(), true );
} else {
wp_enqueue_script( 'fixedtoc-js', plugins_url( 'assets/js/ftoc.min.js', __FILE__ ), array( 'jquery' ), FTOC_VERSION, true );
}
}
/**
* Add inline style.
*
* @since 3.0.0
*
* @return void
*/
public function add_inline_style() {
require_once 'style/class-inline-style.php';
$obj_style = new Fixedtoc_Inline_Style();
$compress = ! FTOC_DEBUG;
$code = $obj_style->get_css( $compress );
if ( empty( $code ) ) {
return;
}
if ( fixedtoc_is_true( 'in_widget' ) ) {
echo "\n<style id=\"fixedtoc-style-inline-css\">";
echo $code;
echo "</style>\n";
} else {
wp_add_inline_style( 'fixedtoc-style', $code );
}
}
/**
* Localize scripts.
*
* @since 3.0.0
*
* @return void
*/
public function localize_scripts() {
$options = array(
'showAdminbar' => is_admin_bar_showing(),
'inOutEffect' => fixedtoc_get_val( 'effects_in_out' ),
'isNestedList' => fixedtoc_is_true( 'nested_list' ),
'isColExpList' => fixedtoc_is_true( 'colexp_list' ),
'showColExpIcon' => fixedtoc_is_true( 'show_colexp_icon' ),
'isAccordionList' => fixedtoc_is_true( 'accordion_list' ),
'isQuickMin' => fixedtoc_is_true( 'quick_min' ),
'isEscMin' => fixedtoc_is_true( 'esc_min' ),
'isEnterMax' => fixedtoc_is_true( 'enter_max' ),
'fixedMenu' => fixedtoc_get_val( 'debug_menu_selector' ),
'scrollOffset' => fixedtoc_get_val( 'debug_scroll_offset' ),
'fixedOffsetX' => fixedtoc_get_val( 'location_horizontal_offset' ),
'fixedOffsetY' => fixedtoc_get_val( 'location_vertical_offset' ),
'fixedPosition' => fixedtoc_get_val( 'location_fixed_position' ),
'contentsFixedHeight' => fixedtoc_get_val( 'contents_fixed_height' ),
'inPost' => fixedtoc_is_true( 'in_post' ),
'contentsFloatInPost' => fixedtoc_get_val( 'contents_float_in_post' ),
'contentsWidthInPost' => fixedtoc_get_val( 'contents_width_in_post' ),
'contentsHeightInPost' => fixedtoc_get_val( 'contents_height_in_post' ),
// 'contentsColexpInit' => fixedtoc_get_val( 'contents_col_exp_init' ),
'contentsColexpInitMobile' => fixedtoc_get_val( 'contents_col_exp_init_mobile' ),
'inWidget' => fixedtoc_is_true( 'in_widget' ),
'fixedWidget' => fixedtoc_is_true( 'fixed_widget' ),
'triggerBorder' => fixedtoc_get_val( 'trigger_border_width' ),
'contentsBorder' => fixedtoc_get_val( 'contents_border_width' ),
'triggerSize' => fixedtoc_get_val( 'trigger_size' ),
'isClickableHeader' => fixedtoc_is_true( 'contents_clickable_header' ),
'debug' => FTOC_DEBUG,
'postContentSelector' => apply_filters( 'fixedtoc_post_content_selector', '#ftwp-postcontent' ),
'mobileMaxWidth' => absint( apply_filters( 'fixedtoc_mobile_max_width', 768 ) ),
// accepted values: 'document-bottom' and 'content-bottom'
'disappearPoint' => apply_filters( 'fixedtoc_disappear_point', 'content-bottom' ),
'smoothScroll' => fixedtoc_is_true( 'smooth_scroll' ),
'scrollDuration' => 500,
'fadeTriggerDuration' => 5000,
);
if ( fixedtoc_is_true( 'in_post' ) || fixedtoc_is_true( 'in_widget' ) ) {
$options['contentsColexpInit'] = fixedtoc_is_true( 'contents_collapse_init' );
}
wp_localize_script( 'fixedtoc-js', 'fixedtocOption', $options );
}
/**
* Modifies headings in the post content.
*
* Inserts HTML id and class attributes.
*
* @since 3.0.0
*
* @param string $content The post content.
*
* @return string
*/
public function modify_post_headings( $content ) {
if ( ! $this->verify_content_context() ) {
return $content;
}
// For Beaver Builder plugin
$is_bbplugin = method_exists( 'FLBuilderModel', 'is_builder_enabled' );
global $page;
$search = array();
$replace = array();
foreach ( $this->data as $datum ) {
if ( $this->is_multipage() && isset( $datum['page'] ) && $page != $datum['page'] ) {
continue;
}
$element = $datum['element'];
// For Beaver Builder plugin
if ( $is_bbplugin ) {
$element = str_replace( '<br />', '', $element );
}
$search[] = '/' . preg_quote( $element, '/' ) . '/';
// Remove class attribute
$new_element = preg_replace( '/(<h[^>]*?)(\s)class(\s*)=(\s*)(["\'])(.*?)(["\'])((.*?)>)/i', '${1}${8}', $element, 1 );
// Assign value to $class_attr
$class_attr = 'ftwp-heading';
if ( $new_element != $element ) {
preg_match( '/<h[^>]*?class(\s*)=(\s*)(["\'])(.*?)(["\'])/i', $element, $match_class );
$class_attr = trim( $match_class[4] ) . ' ' . $class_attr;
}
// Remove id attribute
$new_element = preg_replace( '/(<h[^>]*?)(\s)id(\s*)=(\s*)(["\'])(.*?)(["\'])((.*?)>)/i', '${1}${8}', $new_element, 1 );
// Build $replace
$start = substr( $new_element, 0, 3 );
$end = substr( $new_element, 3 );
$end = str_replace( '$', '\$', $end ); // Fix the heading with the $ character.
$replace[] = $start . ' id="' . esc_attr( $datum['id'] ) . '" class="' . esc_attr( $class_attr ) . '"' . $end;
}
return preg_replace( $search, $replace, $content, 1 );
}
/**
* Wrap the post content with a div element.
*
* @since 3.0.0
*
* @param string $content The post content.
*
* @return string
*/
public function wrap_postcontent( $content ) {
if ( ! $this->verify_content_context() ) {
return $content;
}
return '<div id="ftwp-postcontent">' . $content . '</div>';
}
/**
* Hacks.
*
* @since 3.0.0
*
* @return void
*/
public function hacks() {
?>
<!--[if lte IE 9]>
<script>
'use strict';
(function($) {
$(document).ready(function() {
$('#ftwp-container').addClass('ftwp-ie9');
});
})(jQuery);
</script>
<![endif]-->
<?php
}
/**
* Add shortcode feature.
*
* @since 3.1.0
*
* @return void
*/
private function toc_shortcode() {
require_once 'html/class-element-container-outer.php';
$obj_toc = new Fixedtoc_Dom( new Fixedtoc_Element_Container_outer( $this->data ) );
require_once 'features/class-shortcode.php';
new Fixedtoc_Shortcode( $obj_toc );
}
/**
* Check if 'the_content' hook is the right context for creating TOC.
*
* @since 3.1.16
*
* @return bool
*/
private function verify_content_context() {
global $post;
$post_types = (array) fixedtoc_get_val( 'general_post_types' );
if ( ! isset( $post ) || ! $post instanceof WP_Post || ! in_array( $post->post_type, $post_types ) ) {
return false;
}
return true;
// $compatible = true;
// if ( ! $compatible ) {
// $traces = debug_backtrace();
// if ( $traces['2']['function'] == 'call_user_func_array' ) {
// $target = $traces[5];
// } else {
// $target = $traces[4]; // No 'call_user_func_array' on PHP 7.0+
// }
//
// if ( ( $target['function'] == 'the_content' )) {
// if ( isset( $target['class'] ) && ! empty( $target['class'] ) ) {
// return false;
// }
// return true;
// } else {
// return false;
// }
// } else {
// return true;
// }
}
/**
* Remove extra raw content for generating the data.
* Excludes: code tags.
*
* @since 3.1.18
*
* @param string $content
*
* @return string
*/
private function filter_input_raw_content( $content ) {
return apply_filters( 'fixedtoc_data_raw_content', $content );
// $content = preg_replace( '/<code[^>]*>.*?<\/code>/is', '', $content );
}
/**
* Remove extra content for generating the data.
* Excludes: javascript tags.
*
* @since 3.1.14
*
* @param string $content
*
* @return string
*/
private function filter_input_content( $content ) {
$content = apply_filters( 'fixedtoc_data_content', $content );
return preg_replace( '/<script\b[^>]*>.*?<\/script>/is', '', $content );
}
}