<?php
/**
* This file implements the Table of Contents plugin for b2evolution
*
* b2evolution - {@link http://b2evolution.net/}
* Released under GNU GPL License - {@link http://b2evolution.net/about/gnu-gpl-license}
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
*
* @package plugins
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
/**
* Table of Contents plugin.
*
* @package plugins
*/
class table_contents_plugin extends Plugin
{
var $name;
var $code = 'b2evoTOC';
var $priority = 110;
var $version = '7.2.3';
var $group = 'rendering';
var $subgroup = 'infoitem';
var $short_desc;
var $long_desc;
var $help_topic = 'table-of-contents-plugin';
var $widget_icon = 'list';
var $number_of_installs = 1;
/**
* Init
*/
function PluginInit( & $params )
{
$this->name = T_('Table of Contents');
$this->short_desc = T_('Render table of contents from header html tags.');
$this->long_desc = sprintf( T_('This renderer generates a (nested) bullet list from all %s found in the content by short tag %s'), '<code><Hx id="xxx"></code>', '<code>[toc]</code>' );
}
/**
* Define here default collection/blog settings that are to be made available in the backoffice.
*
* @param array Associative array of parameters.
* @return array See {@link Plugin::get_coll_setting_definitions()}.
*/
function get_coll_setting_definitions( & $params )
{
return array_merge( parent::get_coll_setting_definitions( $params ),
array(
'offset_scroll' => array(
'label' => T_('Anchor offset'),
'type' => 'integer',
'defaultvalue' => 0,
'suffix' => ' px',
'note' => T_('This will be used when scrolling to an anchor.'),
),
)
);
}
/**
* Event handler: Called at the beginning of the skin's HTML HEAD section.
*
* Use this to add any HTML HEAD lines (like CSS styles or links to resource files (CSS, JavaScript, ..)).
*
* @param array Associative array of parameters
*/
function SkinBeginHtmlHead( & $params )
{
global $Collection, $Blog, $disp;
if( ! isset( $Blog ) || (
$this->get_coll_setting( 'coll_apply_rendering', $Blog ) == 'never' &&
$this->get_coll_setting( 'coll_apply_comment_rendering', $Blog ) == 'never' ) )
{ // Don't load css/js files when plugin is not enabled:
return;
}
if( $disp == 'single' || $disp == 'page' )
{ // Initialize JS for better scrolling only on Item's page:
expose_var_to_js( 'evo_plugin_table_contents_settings', '{
offset_scroll: '.format_to_js( intval( $this->get_coll_setting( 'offset_scroll', $Blog ) ) ).'
}' );
}
}
/**
* Perform rendering
*
* @param array Associative array of parameters
* @return boolean true if we can render something for the required output format
*/
function RenderItemAsHtml( & $params )
{
$content = & $params['data'];
// Reset list from previous content:
$this->cached_toc = NULL;
// Data for using inside callback function below:
$this->current_Item = $this->get_Item_from_params( $params );
$this->current_content = $content;
// Replace `[toc]` short tag with nested/bullet list from all found header anchored tags:
$content = replace_outside_code_tags( '#\[toc\]#i', array( $this, 'callback_render_toc' ), $content, 'replace_content_callback' );
return true;
}
/**
* Perform rendering of Message content
*
* @see Plugin::RenderMessageAsHtml()
*/
function RenderMessageAsHtml( & $params )
{
return true;
}
/**
* Perform rendering of Email content
*
* @see Plugin::RenderEmailAsHtml()
*/
function RenderEmailAsHtml( & $params )
{
return true;
}
function FilterCommentContent( & $params )
{
$Comment = & $params['Comment'];
if( in_array( $this->code, $Comment->get_renderers_validated() ) )
{ // Always allow rendering for comment:
$comment_Item = & $Comment->get_Item();
$render_params = array_merge( array( 'data' => & $Comment->content, 'Item' => & $comment_Item ), $params );
$this->RenderItemAsHtml( $render_params );
}
return false;
}
/**
* Generate table of contents from content
*
* @param object Item
* @param string Content
* @return string Rendered content
*/
function genereate_toc( $Item, $content )
{
$toc = '';
if( empty( $Item ) )
{ // Item must be defined for initialize URL:
return $toc;
}
if( preg_match_outcode( '#<h([1-6])[^>]+id=([^>\s]+)[^>]*>(.+?)</h\1>#i', $content, $header_matches ) )
{ // If at least one `<Hx id="xxx">` is found in content:
$item_url = $Item->get_permanent_url();
$min_header_level = min( $header_matches[1] );
$toc .= '<ul class="evo_plugin__table_of_contents">';
foreach( $header_matches[3] as $h => $header_text )
{
$header_text = utf8_strip_tags( $header_text );
$header_text = preg_replace( '#\[[a-z]+:[^\]`]+\]#i', '', $header_text );
$anchor = trim( $header_matches[2][ $h ], '"\'' );
$toc .= '<li style="margin-left:'.( ( $header_matches[1][ $h ] - $min_header_level ) * 10 ).'px">'
.'<a href="'.$item_url.'#'.$anchor.'" data-anchor="'.$anchor.'">'.$header_text.'</a>'
.'</li>';
}
$toc .= '</ul>';
}
return $toc;
}
/**
* Callback function to render table of contents short tag
*
* @param array Matches of `[toc]` short tag
*/
function callback_render_toc( $toc_matches )
{
if( $this->cached_toc === NULL )
{ // Initialize table of contents once for the requested content and store into cache:
$this->cached_toc = $this->genereate_toc( $this->current_Item, $this->current_content );
}
return $this->cached_toc;
}
/**
* Get keys for block/widget caching
*
* Maybe be overriden by some widgets, depending on what THEY depend on..
*
* @param integer Widget ID
* @return array of keys this widget depends on
*/
function get_widget_cache_keys( $widget_ID = 0 )
{
global $Collection, $Blog, $Item;
return array(
'wi_ID' => $widget_ID, // Have the widget settings changed ?
'set_coll_ID' => isset( $Blog ) ? $Blog->ID : NULL, // Have the settings of the blog changed ? (ex: new skin)
'cont_coll_ID' => isset( $Blog ) ? $Blog->ID : NULL, // Has the content of the displayed blog changed ?
'item_ID' => ( empty( $Item->ID ) ? 0 : $Item->ID ), // Has the Item page changed?
);
}
/**
* Get definitions for widget specific editable params
*
* @see Plugin::GetDefaultSettings()
* @param local params like 'for_editing' => true
*/
function get_widget_param_definitions( $params )
{
return array(
'title' => array(
'label' => T_('Title'),
'size' => 60,
'defaultvalue' => T_('On this page'),
),
);
}
/**
* Event handler: SkinTag (widget)
*
* @param array Associative array of parameters.
* @return boolean did we display?
*/
function SkinTag( & $params )
{
global $Item, $disp;
$this->init_widget_params( $params, array(
'block_start' => '<div class="evo_widget $wi_class$ panel panel-default">',
'block_end' => '</div>',
'block_title_start' => '<div class="panel-heading"><h4 class="panel-title">',
'block_title_end' => '</h4></div>',
'block_body_start' => '<div class="panel-body">',
'block_body_end' => '</div>',
) );
if( $disp != 'single' && $disp != 'page' )
{ // Don't display this widget for not post pages:
$this->display_widget_debug_message( 'Plugin widget "'.$this->name.'" is hidden because no proper disp.' );
return false;
}
if( empty( $Item ) )
{ // Don't display this widget when no Item object:
$this->display_widget_debug_message( 'Plugin widget "'.$this->name.'" is hidden because no current Item.' );
return false;
}
// Generate table of contents:
$toc = $this->genereate_toc( $Item, $Item->get_full_content( 'htmlbody', $params ) );
if( empty( $toc ) )
{ // Don't display widget when current Item has no anchor header tags in content:
$this->display_widget_debug_message( 'Plugin widget "'.$this->name.'" is hidden because Item has no anchor header tags in content.' );
return false;
}
echo $params['block_start'];
$widget_title = $this->get_widget_setting( 'title', $params );
if( ! empty( $widget_title ) )
{ // We want to display a title for the widget block:
echo $params['block_title_start'];
echo $widget_title;
echo $params['block_title_end'];
}
echo $params['block_body_start'];
// Display table of contents:
echo $toc;
echo $params['block_body_end'];
echo $params['block_end'];
return true;
}
}
?>