<?php
/**
* This file implements the Widget class.
*
* This file is part of the evoCore framework - {@link http://evocore.net/}
* See also {@link https://github.com/b2evolution/b2evolution}.
*
* @license GNU GPL v2 - {@link http://b2evolution.net/about/gnu-gpl-license}
*
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
*
* @package evocore
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
load_class( '_core/model/dataobjects/_dataobject.class.php', 'DataObject' );
// Load functions for widget layout:
load_funcs( 'widgets/_widgets.funcs.php' );
/**
* ComponentWidget Class
*
* A ComponentWidget is a displayable entity that can be placed into a Container on a web page.
*
* @package evocore
*/
class ComponentWidget extends DataObject
{
/**
* Widget container ID
*/
var $wico_ID;
var $order;
/**
* @var string Type of the plugin ("core" or "plugin")
*/
var $type;
var $code;
var $params;
/**
* Indicates whether the widget is enabled.
*
* @var boolean
*/
var $enabled;
/**
* Array of params which have been customized for this widget instance
*
* This is saved to the DB as a serialized string ($params)
*/
var $param_array = NULL;
/**
* Array of params used during display()
*/
var $disp_params = NULL;
/**
* Lazy instantiated.
*
* This gets set/used for widget plugins (those that hook into SkinTag).
* (false if this Widget is not handled by a Plugin)
* @see get_Plugin()
* @var Plugin
*/
var $Plugin;
/**
* @var BlockCache
*/
var $BlockCache;
/**
* The widget container where this widget belongs to
*
* Lazy instantiated.
*
* @var WidgetContainer
*/
var $WidgetContainer;
/**
* @var Blog
*/
var $Blog = NULL;
/**
* @var target User that should be used depending on context
*/
var $target_User = NULL;
/**
* Widget icon name.
* Use icon name from http://fontawesome.io/icons/
*
* @var string
*/
var $icon = 'cube';
/**
* @var Mode: 'designer' or 'normal'
*/
var $mode = 'normal';
/**
* Constructor
*
* @param object data row from db
*/
function __construct( $db_row = NULL, $type = 'core', $code = NULL )
{
// Call parent constructor:
parent::__construct( 'T_widget__widget', 'wi_', 'wi_ID' );
if( is_null($db_row) )
{ // We are creating an object here:
// Using parent:: instead of $this-> in order to fix http://forums.b2evolution.net//viewtopic.php?p=94778
parent::set( 'type', $type );
parent::set( 'code', $code );
}
else
{ // We are loading an object:
$this->ID = $db_row->wi_ID;
$this->wico_ID = $db_row->wi_wico_ID;
$this->type = $db_row->wi_type;
$this->code = $db_row->wi_code;
$this->params = $db_row->wi_params;
$this->order = $db_row->wi_order;
$this->enabled = $db_row->wi_enabled;
}
}
/**
* Get a member param by its name
*
* @param mixed Name of parameter
* @return mixed Value of parameter
*/
function get( $parname )
{
if( $parname == 'coll_ID' )
{
return $this->get_coll_ID();
}
return parent::get( $parname );
}
/**
* Get param prefix with is used on edit forms and submit data
*
* @return string
*/
function get_param_prefix()
{
return 'edit_widget_'.( empty( $this->ID ) ? '0' : $this->ID ).'_set_';
}
/**
* Get ref to the Plugin handling this Widget.
*
* @return Plugin
*/
function & get_Plugin()
{
global $Plugins;
if( is_null( $this->Plugin ) )
{
if( $this->type != 'plugin' )
{
$this->Plugin = false;
}
else
{
$this->Plugin = & $Plugins->get_by_code( $this->code );
}
}
return $this->Plugin;
}
/**
* Get WidgetContainer
*/
function & get_WidgetContainer()
{
if( ! isset( $this->WidgetContainer ) )
{
$WidgetContainerCache = & get_WidgetContainerCache();
$this->WidgetContainer = & $WidgetContainerCache->get_by_ID( $this->wico_ID, false, false );
}
return $this->WidgetContainer;
}
/**
* Get the collection ID where this widget belongs to
*
* @return integer Collection ID
*/
function get_coll_ID()
{
return $this->get_container_param( 'coll_ID' );
}
/**
* Get param value of container
*
* @param string Param name
* @return string Param value
*/
function get_container_param( $param )
{
$WidgetContainer = & $this->get_WidgetContainer();
if( empty( $WidgetContainer ) )
{
return NULL;
}
return $WidgetContainer->get( $param );
}
/**
* Load params
*/
function load_from_Request()
{
load_funcs('plugins/_plugin.funcs.php');
// Loop through all widget params:
foreach( $this->get_param_definitions( array( 'for_editing' => true, 'for_updating' => true ) ) as $parname => $parmeta )
{
$parvalue = NULL;
if( $parname == 'allow_blockcache'
&& isset( $parmeta['disabled'] )
&& ( $parmeta['disabled'] == 'disabled' ) )
{ // Force checkbox "Allow caching" to unchecked when it is disallowed from widget config
$parvalue = 0;
}
autoform_set_param_from_request( $parname, $parmeta, $this, 'Widget', NULL, $parvalue );
}
}
/**
* Get name of widget
*
* Should be overriden by core widgets
*/
function get_name()
{
if( $this->type == 'plugin' )
{
// Make sure Plugin is loaded:
if( $this->get_Plugin() )
{
return $this->Plugin->name;
}
return T_('Inactive / Uninstalled plugin').': "'.$this->code.'"';
}
elseif( $this->type == 'wrong' )
{
return T_('Wrong widget / Invalid code').': "'.$this->code.'"';
}
return T_('Unknown');
}
/**
* Get a very short desc. Used in the widget list.
*
* MAY be overriden by core widgets. Example: menu link widget.
*/
function get_short_desc()
{
return $this->get_name();
}
/**
* Get widget icon
*
* @return string
*/
function get_icon()
{
if( $this->type == 'plugin' )
{ // Use widget icon from plugin:
if( $this->get_Plugin() )
{ // Get widget icon from plugin:
return $this->Plugin->get_widget_icon();
}
else
{ // Set icon for inactive / uninstalled plugin:
$this->icon = 'warning';
}
}
if( empty( $this->icon ) )
{
return '';
}
return '<span class="label label-info evo_widget_icon"><span class="fa fa-'.$this->icon.'"></span></span>';
}
/**
* Get a clean description to display in the widget list.
* @return string
*/
function get_desc_for_list()
{
$name = $this->get_name();
if( $this->type == 'plugin' )
{ // Plugin widget:
$widget_Plugin = & $this->get_Plugin();
if( $widget_Plugin )
{
if( isset( $this->disp_params['title'] ) && ! empty( $this->disp_params['title'] ) )
{
return $widget_Plugin->get_widget_icon().' <strong>'.$this->disp_params['title'].'</strong> ('.$name. ' - ' .T_('Plugin').')';
}
return $widget_Plugin->get_widget_icon().' <strong>'.$name.'</strong> ('.T_('Plugin').')';
}
else
{
$icon = '<span class="label label-info evo_widget_icon"><span class="fa fa-warning"></span></span>';
return $icon.' <strong>'.$name.'</strong> ('.T_('Plugin').')';
}
}
// Normal widget:
$short_desc = $this->get_short_desc();
$icon = $this->get_icon();
if( $name == $short_desc || empty( $short_desc ) )
{
return $icon.' <strong>'.$name.'</strong>';
}
return $icon.' <strong>'.$short_desc.'</strong> ('.$name.')';
}
/**
* Get desc of widget
*
* Should be overriden by core widgets
*/
function get_desc()
{
if( $this->type == 'plugin' )
{
// Make sure Plugin is loaded:
if( $this->get_Plugin() )
{
return $this->Plugin->short_desc;
}
return T_('Inactive / Uninstalled plugin').': "'.$this->code.'"';
}
return T_('Unknown');
}
/**
* Get help URL
*
* @return string|NULL URL, NULL - when core widget doesn't define the url yet
*/
function get_help_url()
{
if( $widget_Plugin = & $this->get_Plugin() )
{ // Get url of the plugin widget
$help_url = $widget_Plugin->get_help_url( '$widget_url' );
}
else
{ // Core widget must defines this URL
$help_url = NULL;
}
return $help_url;
}
/**
* Get help link
*
* @param string Icon
* @param boolean TRUE - to add info to display it in tooltip on mouseover
* @return string icon
*/
function get_help_link( $icon = 'help', $use_tooltip = true )
{
$widget_url = $this->get_help_url();
if( empty( $widget_url ) )
{ // Return empty string when widget URL is not defined
return '';
}
$link_attrs = array( 'target' => '_blank' );
if( $use_tooltip )
{ // Add these data only for tooltip
$link_attrs['class'] = 'action_icon help_plugin_icon';
$link_attrs['data-popover'] = format_to_output( $this->get_desc(), 'htmlattr' );
}
return action_icon( '', $icon, $widget_url, NULL, NULL, NULL, $link_attrs );
}
/**
* Get definitions for editable params.
*
* @see Plugin::GetDefaultSettings()
*
* @param array Local params like 'for_editing' => true
*/
function get_param_definitions( $params )
{
$r = array();
if( $this->type == 'plugin' )
{
// Make sure Plugin is loaded:
if( $this->get_Plugin() )
{
$r = $this->Plugin->get_widget_param_definitions( $params );
}
}
if( ! isset( $r['widget_css_class'] ) ||
! isset( $r['widget_ID'] ) ||
! isset( $r['allow_blockcache'] ) )
{ // Start fieldset of advanced settings:
$r['advanced_layout_start'] = array(
'layout' => 'begin_fieldset',
'label' => T_('Advanced'),
);
$advanced_layout_is_started = true;
}
// Add advanced definitions if they are provided in a widget:
$r = array_merge( $r, $this->get_advanced_param_definitions() );
if( ! empty( $this->allow_link_css_params ) )
{ // Enable link/button CSS classes only for specific widgets like menu widgets:
$r['widget_link_class'] = array(
'label' => '<span class="dimmed">'.T_('Link/Button Class').'</span>',
'size' => 20,
'note' => sprintf( T_('Replaces %s in class attribute of link/button.'), '<code>$link_class$</code>' ).' '.T_('Leave empty to use default values from skin or from widget.'),
);
$r['widget_active_link_class'] = array(
'label' => '<span class="dimmed">'.T_('Active Link/Button Class').'</span>',
'size' => 20,
'note' => sprintf( T_('Replaces %s in class attribute of active link/button.'), '<code>$link_class$</code>' ).' '.T_('Leave empty to use default values from skin or from widget.'),
);
}
if( ! isset( $r['widget_css_class'] ) )
{ // Widget CSS class:
$r['widget_css_class'] = array(
'label' => '<span class="dimmed">'.T_( 'Widget CSS Class' ).'</span>',
'size' => 20,
'note' => sprintf( T_('Will be injected into %s in your skin containers (along with required system classes).'), '<code>$wi_class$</code>' ),
);
}
if( ! isset( $r['widget_ID'] ) )
{ // Widget ID:
$r['widget_ID'] = array(
'label' => '<span class="dimmed">'.T_( 'Widget DOM ID' ).'</span>',
'size' => 20,
'note' => sprintf( T_('Replaces %s in your skins containers.'), '<code>$wi_ID$</code>' ).' '.sprintf( T_('Leave empty to use default value: %s.'), '<code>widget_'.$this->type.'_'.$this->code.'_'.$this->ID.'</code>' ),
);
}
if( ! isset( $r['allow_blockcache'] ) )
{ // Allow widget/block caching:
$widget_Blog = & $this->get_Blog();
$r['allow_blockcache'] = array(
'label' => T_( 'Allow caching' ),
'note' => ( $widget_Blog && $widget_Blog->get_setting( 'cache_enabled_widgets' ) ) ?
T_('Uncheck to prevent this widget from ever being cached in the block cache. (The whole page may still be cached.) This is only needed when a widget is poorly handling caching and cache keys.') :
T_('Block caching is disabled for this collection.'),
'type' => 'checkbox',
'defaultvalue' => true,
);
}
if( ! empty( $advanced_layout_is_started ) )
{ // End fieldset of advanced settings:
$r['advanced_layout_end'] = array(
'layout' => 'end_fieldset',
);
$advanced_layout_is_started = false;
}
return $r;
}
/**
* Get advanced definitions for editable params.
*
* @see Plugin::GetDefaultSettings()
*
* @return array Advanced params
*/
function get_advanced_param_definitions()
{
return array();
}
/**
* Load param array.
*/
function load_param_array()
{
if( is_null( $this->param_array ) )
{ // Param array has not been loaded yet
$this->param_array = @unserialize( $this->params );
if( empty( $this->param_array ) )
{ // No saved param values were found:
$this->param_array = array();
}
}
}
/**
* Get param value.
*
* @param string Parameter name
* @param mixed Default value, Set to different than NULL only if it is called from a widget::get_param_definition() function to avoid infinite loop
* @param string|NULL Group name
* @return mixed
*/
function get_param( $parname, $default_value = NULL, $group = NULL )
{
$this->load_param_array();
if( strpos( $parname, '[' ) !== false )
{ // Get value for array setting like "sample_sets[0][group_name_param_name]":
$setting_names = explode( '[', $parname );
if( isset( $this->param_array[ $setting_names[0] ] ) )
{
$setting_value = $this->param_array[ $setting_names[0] ];
unset( $setting_names[0] );
foreach( $setting_names as $setting_name )
{
$setting_name = trim( $setting_name, ']' );
if( isset( $setting_value[ $setting_name ] ) )
{
$setting_value = $setting_value[ $setting_name ];
}
else
{
$setting_value = NULL;
break;
}
}
return $setting_value;
}
}
else
{ // Get normal(not array) setting value:
if( isset( $this->disp_params[ $parname ] ) )
{ // Get an overridden value from skin:
return $this->disp_params[ $parname ];
}
elseif( isset( $this->param_array[ $parname ] ) )
{ // Get value from DB:
return $this->param_array[ $parname ];
}
}
if( $default_value !== NULL )
{ // Use defined default value when it is not saved in DB yet:
// (This call is used to get a value from function widget::get_param_definition() to avoid infinite loop)
return $default_value;
}
// Try default values from widget config:
$params = $this->get_param_definitions( NULL );
if( $group === NULL )
{ // Get param from simple field:
if( isset( $params[$parname]['defaultvalue'] ) )
{ // We have a default value:
return $params[$parname]['defaultvalue'] ;
}
}
else
{ // Get param from group field:
$parname = substr( $parname, strlen( $group ) );
if( isset( $params[$group]['inputs'][$parname]['defaultvalue'] ) )
{ // We have a default value:
return $params[$group]['inputs'][$parname]['defaultvalue'] ;
}
}
return NULL;
}
/**
* Set param value
*
* @param string parameter name
* @param mixed parameter value
* @param boolean true to set to NULL if empty value
* @return boolean true, if a value has been set; false if it has not changed
*/
function set( $parname, $parvalue, $make_null = false, $group = NULL )
{
$params = $this->get_param_definitions( NULL );
if( isset( $params[$parname] ) ||
( $group !== NULL && isset( $params[ $group ]['inputs'][ substr( $parname, strlen( $group ) ) ] ) ) )
{ // This is a widget specific param:
// Make sure param_array is loaded before set the param value
$this->load_param_array();
$this->param_array[$parname] = $parvalue;
// This is what'll be saved to the DB:
return $this->set_param( 'params', 'string', serialize($this->param_array), $make_null );
}
switch( $parname )
{
default:
return $this->set_param( $parname, 'string', $parvalue, $make_null );
}
}
/**
* Request all required css and js files for this widget
*/
function request_required_files()
{
}
/**
* Prepare display params
*
* @todo Document default params and default values.
* @todo fp> do NOT call this when just listing widget names in the back-office. It's overkill!
*
* @param array MUST contain at least the basic display params
*/
function init_display( $params )
{
global $admin_url, $debug;
if( !is_null($this->disp_params) )
{ // Params have been initialized before...
return;
}
// Generate widget defaults (editable params) array:
$widget_defaults = array();
$defs = $this->get_param_definitions( array() );
foreach( $defs as $parname => $parmeta )
{
if( isset( $parmeta['type'] ) && $parmeta['type'] == 'checklist' )
{
$widget_defaults[ $parname ] = array();
foreach( $parmeta['options'] as $parmeta_option )
{
$widget_defaults[ $parname ][ $parmeta_option[0] ] = $parmeta_option[2];
}
}
else
{
$widget_defaults[ $parname ] = ( isset( $parmeta['defaultvalue'] ) ) ? $parmeta['defaultvalue'] : NULL;
}
}
// Load DB configuration:
$this->load_param_array();
// DEFAULT CONTAINER PARAMS:
// Merge basic defaults < widget defaults (editable params) < container params < DB params
// note: when called with skin_widget it falls back to basic defaults < widget defaults < calltime params < array()
$params = array_merge( array(
'widget_context' => 'general', // general | item | user
'block_start' => '<div class="evo_widget widget $wi_class$">',
'block_end' => '</div>',
'block_display_title' => true,
'block_title_start' => '<h3>',
'block_title_end' => '</h3>',
'block_body_start' => '',
'block_body_end' => '',
'collist_start' => '',
'collist_end' => '',
'coll_start' => '<h4>',
'coll_end' => '</h4>',
'list_start' => '<ul>',
'list_end' => '</ul>',
'item_start' => '<li>',
'item_end' => '</li>',
'item_title_start' => '<strong>',
'item_title_end' => ':</strong> ',
'link_default_class' => 'default',
'link_selected_class' => 'selected',
'item_text_start' => '',
'item_text_end' => '',
'item_text' => '%s',
'item_selected_start' => '<li class="selected">',
'item_selected_end' => '</li>',
'item_selected_text' => '%s',
// Automatically detect whether we are displaying menu links as list elements or as standalone buttons:
'inlist' => 'auto', // auto is based on 'list_start'; may also be true or false
// Button styles used for Menu Links / Buttons widgets:
'button_default_class' => 'btn btn-default btn-margin-right',
'button_selected_class' => 'btn btn-default btn-margin-right active',
'button_group_start' => '<span class="btn-group">',
'button_group_end' => '</span>',
// Tabs style:
'tabs_start' => '<ul class="nav nav-tabs">',
'tabs_end' => '</ul>',
'tab_start' => '<li>',
'tab_end' => '</li>',
'tab_selected_start' => '<li class="active">',
'tab_selected_end' => '</li>',
'tab_default_class' => '',
'tab_selected_class' => 'active',
'grid_start' => '<table cellspacing="1" class="widget_grid">',
'grid_colstart' => '<tr>',
'grid_cellstart' => '<td>',
'grid_cellend' => '</td>',
'grid_colend' => '</tr>',
'grid_end' => '</table>',
'grid_nb_cols' => 2,
'flow_start' => '<div class="widget_flow_blocks">',
'flow_block_start' => '<div>',
'flow_block_end' => '</div>',
'flow_end' => '</div>',
'rwd_start' => '<div class="widget_rwd_blocks row">',
'rwd_block_start' => '<div class="$wi_rwd_block_class$"><div class="widget_rwd_content clearfix">',
'rwd_block_end' => '</div></div>',
'rwd_end' => '</div>',
'thumb_size' => 'crop-80x80',
'link_type' => 'canonic', // 'canonic' | 'context' (context will regenrate URL injecting/replacing a single filter)
'item_selected_text_start' => '',
'item_selected_text_end' => '',
'group_start' => '<ul>',
'group_end' => '</ul>',
'group_item_start' => '<li>',
'group_item_end' => '</li>',
'notes_start' => '<div class="notes">',
'notes_end' => '</div>',
'tag_cloud_start' => '<p class="tag_cloud">',
'tag_cloud_end' => '</p>',
'limit' => 100,
), $widget_defaults, $params, $this->param_array );
if( isset( $params['override_params_for_'.$this->code] ) )
{ // Use specific widget params if they are defined for this widget by code:
$params = array_merge( $params, $params['override_params_for_'.$this->code] );
}
// Customize params to the current widget:
// Add additional css classes if required:
$widget_css_class = 'widget_'.$this->type.'_'.$this->code.( empty( $params[ 'widget_css_class' ] ) ? '' : ' '.$params[ 'widget_css_class' ] );
// Set additional css class depending on layout:
$layout = isset( $params['layout'] ) ? $params['layout'] : ( isset( $params['thumb_layout'] ) ? $params['thumb_layout'] : NULL );
switch( $layout )
{
case 'rwd':
$widget_css_class .= ' evo_layout_rwd';
break;
case 'flow':
$widget_css_class .= ' evo_layout_flow';
break;
case 'list':
$widget_css_class .= ' evo_layout_list';
break;
case 'grid':
$widget_css_class .= ' evo_layout_grid';
break;
}
// Add custom id if required, default to generic id for validation purposes:
$widget_ID = ( !empty($params[ 'widget_ID' ]) ? $params[ 'widget_ID' ] : 'widget_'.$this->type.'_'.$this->code.'_'.$this->ID );
// Replace the values:
$this->disp_params = str_replace( array( '$wi_ID$', '$wi_class$' ), array( $widget_ID, $widget_css_class ), $params );
}
/**
* Convert old display params to new name.
*
* Use this function if some params were renamed.
* This function will look for the old params and convert them if no new param is present
*/
function convert_legacy_param( $old_name, $new_name )
{
//pre_dump( $this->disp_params );
if( isset($this->disp_params[$old_name]) && !isset($this->disp_params[$new_name]) )
{ // We have old param but NOT new param, duplicate old to new:
$this->disp_params[$new_name] = $this->disp_params[$old_name];
}
}
/**
* Display the widget!
*
* Should be overriden by core widgets
*
* @todo fp> handle custom params for each widget
*
* @param array MUST contain at least the basic display params
* @return bool true if the widget displayed something (other than a debug message)
*/
function display( $params )
{
global $Collection, $Blog;
global $Plugins;
global $rsc_url;
// prepare for display:
$this->init_display( $params );
switch( $this->type )
{
case 'plugin':
// Set widget ID param to make it available in plugin function SkinTag():
$this->disp_params['wi_ID'] = $this->ID;
// Call plugin (will return false if Plugin is not enabled):
if( $Plugins->call_by_code( $this->code, $this->disp_params ) )
{
return true;
}
else
{ // Plugin failed (happens when a plugin has been disabled for example):
if( $this->mode == 'designer' )
{ // Display red text in customizer widget designer mode in order to make this plugin visible for editing:
echo $this->disp_params['block_start'].get_rendering_error( T_('Inactive / Uninstalled plugin').': "'.$this->code.'"', 'span' ).$this->disp_params['block_end'];
}
return false;
}
}
echo "Widget $this->type : $this->code did not provide a display() method! ";
return false;
}
/**
* Wraps display in a cacheable block.
*
* @param array MUST contain at least the basic display params
* @param array of extra keys to be used for cache keying
*/
function display_with_cache( $params, $keys = array() )
{
global $Collection, $Blog, $Timer, $debug, $admin_url, $Session;
$this->init_display( $params );
// Display the debug conatainers when $debug = 2 OR when it is turned on from evo menu under "Collection" -> "Show/Hide containers"
$display_containers = ( $debug == 2 ) || ( is_logged_in() && $Session->get( 'display_containers_'.$Blog->ID ) );
$force_nocaching = false;
// Enable the desinger mode when it is turned on from evo menu under "Designer Mode/Exit Designer" or "Collection" -> "Enable/Disable designer mode"
if( is_logged_in() && $Session->get( 'designer_mode_'.$Blog->ID ) )
{ // Initialize data which is used by JavaScript to build overlay designer mode html elements:
$designer_mode_data = array(
'data-id' => $this->ID,
'data-type' => $this->get_name(),
'data-container' => $this->get_container_param( 'code' ),
);
if( $this->get( 'code' ) == 'subcontainer' &&
( $sub_WidgetContainer = & $this->get_sub_WidgetContainer() ) )
{ // For Sub-Container widget we should know what sub-container is used in order to list and add widgets on customizer mode:
$designer_mode_data['data-subcontainer-name'] = $sub_WidgetContainer->get( 'name' );
$designer_mode_data['data-subcontainer-code'] = $this->get_param( 'container' );
}
// Set data to know current user has a permission to edit this widget:
$designer_mode_data['data-can-edit'] = check_user_perm( 'blog_properties', 'edit', false, $Blog->ID ) ? 1 : 0;
// Don't load a widget content from cache when designer mode is enabled:
$force_nocaching = true;
// Set designer mode:
$this->mode = 'designer';
// Set this param for plugin widgets:
$this->disp_params['debug_mode'] = $this->mode;
}
if( $force_nocaching
|| ! $Blog->get_setting( 'cache_enabled_widgets' )
|| ! $this->disp_params['allow_blockcache']
|| $this->get_cache_status() == 'disallowed' )
{ // NO CACHING - We do NOT want caching for this collection or for this specific widget:
if( $display_containers )
{ // DEBUG:
$is_subcontainer = ( $this->get( 'code' ) == 'subcontainer' || $this->get( 'code' ) == 'subcontainer_row' );
echo '<div class="dev-blocks '.( $is_subcontainer ? 'dev-blocks--subcontainer' : 'dev-blocks--widget' ).'"><div class="dev-blocks-name" title="'.
( $Blog->get_setting('cache_enabled_widgets') ? 'Widget params have BlockCache turned off' : 'Collection params have BlockCache turned off' ).'">';
if( check_user_perm( 'blog_properties', 'edit', false, $Blog->ID ) )
{ // Display a link to edit this widget only if current user has a permission:
echo '<span class="dev-blocks-action"><a href="'.$admin_url.'?ctrl=widgets&action=edit&wi_ID='.$this->ID.'">Edit</a></span>';
}
echo 'Widget: <b>'.$this->get_name().'</b> - Cache OFF <i class="fa fa-info">?</i></div>'."\n";
}
// Start to collect output buffer in order to can clean up rendering errors when it need below:
ob_start();
if( ! empty( $designer_mode_data ) )
{ // Append designer mode html tag attributes to first not empty widget wrapper/container:
$widget_wrappers = array(
'block_start',
'block_body_start',
'list_start',
array( 'item_start', 'item_selected_start' ),
);
foreach( $widget_wrappers as $widget_wrapper_items )
{
if( ! is_array( $widget_wrapper_items ) )
{
$widget_wrapper_items = array( $widget_wrapper_items );
}
$wrapper_is_found = false;
foreach( $widget_wrapper_items as $widget_wrapper )
{
if( !empty( $params[ $widget_wrapper ] ) || !empty( $params['override_params_for_'.$this->code][ $widget_wrapper ] ) )
{ // Append new data for widget wrapper:
$attrib_actions = array(
'data-id' => 'replace',
'data-type' => 'replace',
'data-container' => 'replace',
'data-can-edit' => 'replace',
);
if( !empty( $params[ $widget_wrapper ] ) )
{ // If this wrapper is filled and used with current widget,
$params[ $widget_wrapper ] = update_html_tag_attribs( $params[ $widget_wrapper ], $designer_mode_data, $attrib_actions );
}
if( !empty( $params['override_params_for_'.$this->code][ $widget_wrapper ] ) )
{ // Also update override params:
$params['override_params_for_'.$this->code][ $widget_wrapper ] = update_html_tag_attribs( $params['override_params_for_'.$this->code][ $widget_wrapper ], $designer_mode_data, $attrib_actions );
}
if( isset( $this->disp_params[ $widget_wrapper ] ) )
{ // Also update params if they already have been initialized before:
$this->disp_params[ $widget_wrapper ] = update_html_tag_attribs( $this->disp_params[ $widget_wrapper ], $designer_mode_data, $attrib_actions );
}
$wrapper_is_found = true;
}
}
if( $wrapper_is_found )
{ // Stop search other wrapper in order to use only first filled wrapper:
break;
}
}
if( ! $wrapper_is_found )
{ // Display error if widget has no wrappers to enable designer mode:
echo ' '.get_rendering_error( 'Widget <code>'.$this->code.'</code> cannot be manipulated because it lacks a wrapper tag.', 'span' ).' ';
}
}
if( ! isset( $params['widget_'.$this->code.'_display'] ) || ! empty( $params['widget_'.$this->code.'_display'] ) )
{ // Display widget content:
$this->display( $params );
}
else
{ // Hide the widget by code if it is requsted from skin:
$this->display_debug_message( 'Widget "'.$this->get_name().'" is hidden by code <code>'.$this->code.'</code> from skin template.' );
}
$widget_content = ob_get_clean();
if( ! check_user_perm( 'blog_admin', 'edit', false, $Blog->ID ) )
{ // Clean up rendering errors from content if current User is not collection admin:
$widget_content = clear_rendering_errors( $widget_content );
}
echo $widget_content;
if( $display_containers )
{ // DEBUG:
echo "</div>\n";
}
}
else
{ // Instantiate BlockCache:
$Timer->resume( 'BlockCache' );
// Extend cache keys:
$keys += $this->get_cache_keys();
$this->BlockCache = new BlockCache( 'widget', $keys );
$content = $this->BlockCache->check();
$Timer->pause( 'BlockCache' );
if( $content !== false )
{ // cache hit, let's display:
if( $display_containers )
{ // DEBUG:
echo '<div class="dev-blocks dev-blocks--widget dev-blocks--widget--incache"><div class="dev-blocks-name" title="Cache key = '.$this->BlockCache->serialized_keys.'">';
if( check_user_perm( 'blog_properties', 'edit', false, $Blog->ID ) )
{ // Display a link to edit this widget only if current user has a permission:
echo '<span class="dev-blocks-action"><a href="'.$admin_url.'?ctrl=widgets&action=edit&wi_ID='.$this->ID.'">Edit</a></span>';
}
echo 'Widget: <b>'.$this->get_name().'</b> - FROM cache <i class="fa fa-info">?</i></div>'."\n";
}
if( ! check_user_perm( 'blog_admin', 'edit', false, $Blog->ID ) )
{ // Clean up rendering errors from content if current User is not collection admin:
$content = clear_rendering_errors( $content );
}
echo $content;
if( $display_containers )
{ // DEBUG:
echo "</div>\n";
}
}
else
{ // Cache miss, we have to generate:
if( $display_containers )
{ // DEBUG:
echo '<div class="dev-blocks dev-blocks--widget dev-blocks--widget--notincache"><div class="dev-blocks-name" title="Cache key = '.$this->BlockCache->serialized_keys.'">';
if( check_user_perm( 'blog_properties', 'edit', false, $Blog->ID ) )
{ // Display a link to edit this widget only if current user has a permission:
echo '<span class="dev-blocks-action"><a href="'.$admin_url.'?ctrl=widgets&action=edit&wi_ID='.$this->ID.'">Edit</a></span>';
}
echo 'Widget: <b>'.$this->get_name().'</b> - NOT in cache <i class="fa fa-info">?</i></div>'."\n";
}
$this->BlockCache->start_collect();
if( ! isset( $params['widget_'.$this->code.'_display'] ) || ! empty( $params['widget_'.$this->code.'_display'] ) )
{ // Display widget content:
$this->display( $params );
}
else
{ // Hide the widget by code if it is requsted from skin:
$this->display_debug_message( 'Widget "'.$this->get_name().'" is hidden by code <code>'.$this->code.'</code> from skin template.' );
}
// Save collected cached data if needed:
$content = $this->BlockCache->end_collect( false );
if( ! check_user_perm( 'blog_admin', 'edit', false, $Blog->ID ) )
{ // Clean up rendering errors from content if current User is not collection admin:
$content = clear_rendering_errors( $content );
}
echo $content;
if( $display_containers )
{ // DEBUG:
echo "</div>\n";
}
}
}
}
/**
* Maybe be overriden by some widgets, depending on what THEY depend on..
*
* @return array of keys this widget depends on
*/
function get_cache_keys()
{
global $Collection, $Blog;
if( $this->type == 'plugin' && $this->get_Plugin() )
{ // Get widget cache keys from plugin:
return $this->Plugin->get_widget_cache_keys( $this->ID );
}
return array(
'wi_ID' => $this->ID, // Have the widget settings changed ?
'set_coll_ID' => $Blog->ID, // Have the settings of the blog changed ? (ex: new skin)
);
}
/**
* Get cache status
*
* @param boolean TRUE to check if blog allows a caching for widgets
* @return string 'enabled', 'disabled', 'disallowed', 'denied'
*/
function get_cache_status( $check_blog_restriction = false )
{
$default_widget_params = $this->get_param_definitions( array() );
if( ! empty( $default_widget_params )
&& isset( $default_widget_params['allow_blockcache'] )
&& isset( $default_widget_params['allow_blockcache']['disabled'] )
&& ( $default_widget_params['allow_blockcache']['disabled'] == 'disabled' ) )
{ // Widget cache is NOT allowed by widget config
return 'disallowed';
}
else
{ // Check current cache status if it is allowed
if( $check_blog_restriction )
{ // Check blog restriction for widget caching
$widget_Blog = & $this->get_Blog();
if( $widget_Blog && ! $widget_Blog->get_setting( 'cache_enabled_widgets' ) )
{ // Widget/block cache is not allowed by collection setting:
return 'denied';
}
}
if( $this->get_param( 'allow_blockcache' ) )
{ // Enabled
return 'enabled';
}
else
{ // Disabled
return 'disabled';
}
}
}
/**
* Note: a container can prevent display of titles with 'block_display_title'
* This is useful for the lists in the headers
* fp> I'm not sure if this param should be overridable by widgets themselves (priority problem)
* Maybe an "auto" setting.
*
* @access protected
*/
function disp_title( $title = NULL, $display = true )
{
if( is_null($title) )
{
$title = & $this->disp_params['title'];
}
if( $this->disp_params['block_display_title'] && !empty( $title ) )
{
$r = $this->disp_params['block_title_start'];
if( ! isset( $this->disp_params['hide_header_title'] ) )
{
$r .= format_to_output( $title );
}
$r .= $this->disp_params['block_title_end'];
if( $display ) echo $r;
return $r;
}
}
/**
* List of collections/blogs
*
* @param array MUST contain at least the basic display params
*/
function disp_coll_list( $filter = 'public', $order_by = 'ID', $order_dir = 'ASC' )
{
/**
* @var Blog
*/
global $Collection, $Blog, $baseurl;
echo $this->disp_params['block_start'];
$this->disp_title();
/**
* @var BlogCache
*/
$BlogCache = & get_BlogCache();
if( $filter == 'owner' )
{ // Load blogs of same owner
$blog_array = $BlogCache->load_owner_blogs( $Blog->owner_user_ID, $order_by, $order_dir );
}
else
{ // Load all public blogs
$blog_array = $BlogCache->load_public( $order_by, $order_dir );
}
// 3.3? if( $this->disp_params['list_type'] == 'list' )
// fp> TODO: init default value for $this->disp_params['list_type'] to avoid error
{
echo $this->disp_params['list_start'];
foreach( $blog_array as $l_blog_ID )
{ // Loop through all public blogs:
$l_Blog = & $BlogCache->get_by_ID( $l_blog_ID );
if( $Blog && $l_blog_ID == $Blog->ID )
{ // This is the blog being displayed on this page:
echo $this->disp_params['item_selected_start'];
$link_class = empty( $this->disp_params['widget_active_link_class'] ) ? $this->disp_params['link_selected_class'] : $this->disp_params['widget_active_link_class'];
}
else
{
echo $this->disp_params['item_start'];
$link_class = empty( $this->disp_params['widget_link_class'] ) ? $this->disp_params['link_default_class'] : $this->disp_params['widget_link_class'];
}
echo '<a href="'.$l_Blog->gen_blogurl().'" class="'.$link_class.'" title="'
.$l_Blog->dget( 'name', 'htmlattr' ).'">';
if( $Blog && $l_blog_ID == $Blog->ID )
{ // This is the blog being displayed on this page:
echo $this->disp_params['item_selected_text_start'];
printf( $this->disp_params['item_selected_text'], $l_Blog->dget( 'shortname', 'htmlbody' ) );
echo $this->disp_params['item_selected_text_end'];
echo '</a>';
echo $this->disp_params['item_selected_end'];
}
else
{
echo $this->disp_params['item_text_start'];
printf( $this->disp_params['item_text'], $l_Blog->dget( 'shortname', 'htmlbody' ) );
echo $this->disp_params['item_text_end'];
echo '</a>';
echo $this->disp_params['item_end'];
}
}
echo $this->disp_params['list_end'];
}
/* 3.3?
Problems:
-In FF3/XP with skin evoCamp, I click to drop down and it already reloads the page on the same blog.
-Missing appropriate CSS so it displays at least half nicely in most of teh default skins
{
$select_options = '';
foreach( $blog_array as $l_blog_ID )
{ // Loop through all public blogs:
$l_Blog = & $BlogCache->get_by_ID( $l_blog_ID );
// Add item select list:
$select_options .= '<option value="'.$l_blog_ID.'"';
if( $Blog && $l_blog_ID == $Blog->ID )
{
$select_options .= ' selected="selected"';
}
$select_options .= '>'.$l_Blog->dget( 'shortname', 'formvalue' ).'</option>'."\n";
}
if( !empty($select_options) )
{
echo '<form action="'.$baseurl.'" method="get">';
echo '<select name="blog" onchange="this.form.submit();">'.$select_options.'</select>';
echo '<noscript><input type="submit" value="'.T_('Go').'" /></noscript></form>';
}
}
*/
echo $this->disp_params['block_end'];
}
/**
* Insert object into DB based on previously recorded changes.
*
* @return boolean true on success
*/
function dbinsert()
{
global $DB;
if( $this->ID != 0 )
{
debug_die( 'Existing object cannot be inserted!' );
}
$DB->begin();
if( ! isset( $this->order ) )
{
$order_max = $DB->get_var(
'SELECT MAX(wi_order)
FROM T_widget__widget
WHERE wi_wico_ID = '.$this->wico_ID, 0, 0, 'Get current max order' );
$this->set( 'order', $order_max+1 );
}
$res = parent::dbinsert();
$DB->commit();
return $res;
}
/**
* Update the DB based on previously recorded changes
*/
function dbupdate()
{
$result = parent::dbupdate();
if( $result )
{ // If widget has been really updated
// BLOCK CACHE INVALIDATION:
// This widget has been modified, cached content depending on it should be invalidated:
BlockCache::invalidate_key( 'wi_ID', $this->ID );
}
return $result;
}
/**
* Get Blog
*
* @return object Blog
*/
function & get_Blog()
{
if( $this->Blog === NULL )
{ // Get blog only first time
$BlogCache = & get_BlogCache();
$this->Blog = & $BlogCache->get_by_ID( $this->get_coll_ID(), false, false );
}
return $this->Blog;
}
/**
* Get current layout
*
* @return string|NULL Widget layout | NULL - if widget has no layout setting
*/
function get_layout()
{
return get_widget_layout( $this->disp_params );
}
/**
* Get start of layout
*
* @return string
*/
function get_layout_start()
{
return get_widget_layout_start( $this->disp_params );
}
/**
* Get end of layout
*
* @param integer Cell index (used for grid/table layout)
* @return string
*/
function get_layout_end( $cell_index = 0 )
{
return get_widget_layout_end( $cell_index, $this->disp_params );
}
/**
* Get item start of layout
*
* @param integer Cell index (used for grid/table layout)
* @param boolean TRUE if current item/cell is selected
* @param string Prefix for param
* @return string
*/
function get_layout_item_start( $cell_index = 0, $is_selected = false, $disp_param_prefix = '' )
{
return get_widget_layout_item_start( $cell_index, $is_selected, $disp_param_prefix, $this->disp_params );
}
/**
* Get item end of layout
*
* @param integer Cell index (used for grid/table layout)
* @param boolean TRUE if current item/cell is selected
* @param string Prefix for param
* @return string
*/
function get_layout_item_end( $cell_index = 0, $is_selected = false, $disp_param_prefix = '' )
{
return get_widget_layout_item_end($cell_index, $is_selected, $disp_param_prefix, $this->disp_params );
}
/**
* Get User that should be used for this widget currently depending on context
*
* @return object User
*/
function & get_target_User()
{
if( $this->target_User === NULL )
{ // Initialize target User only first time:
global $Item, $Blog;
if( $this->disp_params['widget_context'] == 'user' )
{ // Use an user of current page disp=user (Only if we are in the context of displaying an User, not if $User is set from before):
$user_ID = get_param( 'user_ID' );
if( empty( $user_ID ) && is_logged_in() )
{ // Use current logged in User:
global $current_User;
$user_ID = $current_User->ID;
}
if( ! empty( $user_ID ) )
{ // Try to get User by ID:
$UserCache = & get_UserCache();
$this->target_User = & $UserCache->get_by_ID( $user_ID, false, false );
}
}
if( empty( $this->target_User ) && $this->disp_params['widget_context'] == 'item' && ! empty( $Item ) )
{ // Use an author of the current $Item (Only if we are in the context of displaying an Item, not if $Item is set from before):
$this->target_User = & $Item->get_creator_User();
}
if( empty( $this->target_User ) && ! empty( $Blog ) )
{ // Use an owner of the current $Blog:
$this->target_User = & $Blog->get_owner_User();
}
}
return $this->target_User;
}
/**
* Get the list of validated renderers for this Widget. This includes stealth plugins etc.
*
* @return array List of validated renderer codes
*/
function get_renderers_validated()
{
if( ! isset( $this->renderers_validated ) )
{
global $Plugins;
$widget_Blog = & $this->get_Blog();
// Convert active renderers options for plugin functions below:
$widget_renderers = array_keys( $this->disp_params['renderers'] );
$this->renderers_validated = $Plugins->validate_renderer_list( $widget_renderers, array(
'Blog' => & $widget_Blog,
'setting_name' => 'shared_apply_rendering'
) );
}
return $this->renderers_validated;
}
/**
* Get content which is rendered with plugins
*
* @param string Source content
* @return string Rendered content
*/
function get_rendered_content( $content )
{
if( ! isset( $this->disp_params['renderers'] ) )
{ // This widget has no render settings, Return original content:
return $content;
}
global $Plugins;
$widget_Blog = & $this->get_Blog();
if( empty( $widget_Blog ) )
{ // Use current collection if it is not defined, e.g. for shared widget containers:
global $Blog;
$widget_Blog = $Blog;
}
$widget_renderers = $this->get_renderers_validated();
// Do some optional filtering on the content
// Typically stuff that will help the content to validate
// Useful for code display.
// Will probably be used for validation also.
// + APPLY RENDERING from Rendering Plugins:
$Plugins_admin = & get_Plugins_admin();
$params = array(
'object_type' => 'Widget',
'object' => & $this,
'object_Blog' => & $widget_Blog
);
$fake_title = '';
$Plugins_admin->filter_contents( $fake_title /* by ref */, $content /* by ref */, $widget_renderers, $params /* by ref */ );
// Render block content with selected plugins:
$Plugins->render( $content, $widget_renderers, 'htmlbody', array( 'Blog' => & $widget_Blog, 'Widget' => $this ), 'Render' );
$Plugins->render( $content, $widget_renderers, 'htmlbody', array( 'Blog' => & $widget_Blog, 'Widget' => $this ), 'Display' );
return $content;
}
/**
* Get JavaScript code which helps to edit widget form
*
* @return string
*/
function get_edit_form_javascript()
{
return false;
}
/**
* Get a form display mode depending on requested skin param 'form_display'
*
* @param string Default value for form display: 'standard', 'compact', 'nolabels', 'inline', 'grouped'
* @return string Current form display: 'standard', 'compact', 'nolabels', 'inline', 'grouped' or another default of the widget
*/
function get_form_display( $default_form_display = 'standard' )
{
$form_display = isset( $this->disp_params['form_display'] ) ? $this->disp_params['form_display'] : $default_form_display;
return in_array( $form_display, array( 'standard', 'compact', 'nolabels', 'inline', 'grouped' ) ) ? $form_display : $default_form_display;
}
/**
* Get Item's info from param by ID or slug
*
* @param string Param name
* @return string
*/
function get_param_item_info( $param_name )
{
$param_value = $this->get_param( $param_name, '' );
if( empty( $param_value ) )
{ // Param is not defined:
return '';
}
$ItemCache = & get_ItemCache();
$param_value_is_ID = is_number( $param_value );
if( ! ( $param_value_is_ID && $param_Item = & $ItemCache->get_by_ID( $param_value, false, false ) ) &&
! ( ! $param_value_is_ID && $param_Item = & $ItemCache->get_by_urltitle( $param_value, false, false ) ) )
{ // Item is not detected:
return get_rendering_error( T_('Item is not found.'), 'span' );
}
$item_info = '';
$status_icons = get_visibility_statuses( 'icons' );
if( isset( $status_icons[ $param_Item->get( 'status' ) ] ) )
{ // Status colored icon:
$item_info .= $status_icons[ $param_Item->get( 'status' ) ];
}
// Title with link to permament url:
$item_info .= ' '.$param_Item->get_title( array( 'link_type' => 'admin_view' ) );
// Icon to edit:
$item_info .= ' '.$param_Item->get_edit_link( array( 'text' => '#icon#' ) );
return $item_info;
}
/**
* Display debug message e-g on designer mode when we need to show widget when nothing to display currently
*
* @param string Message
*/
function display_debug_message( $message = NULL )
{
if( $this->mode == 'designer' )
{ // Display message on designer mode:
if( $message === NULL )
{ // Set default message:
$message = 'Widget "'.$this->get_name().'" is hidden because there is nothing to display.';
}
if( preg_match( '#class="[^"]*evo_widget[^"]*"#i', $this->disp_params['block_start'].$this->disp_params['block_body_start'] ) )
{ // If standard widget wrappers have special style class "evo_widget" we can use it:
echo $this->disp_params['block_start'];
$this->disp_title();
echo $this->disp_params['block_body_start'];
echo $message;
echo $this->disp_params['block_body_end'];
echo $this->disp_params['block_end'];
}
else
{ // Otherwise we should use more wrappers to design widgets correctly, e-g for Menu container:
echo $this->disp_params['block_start'];
$this->disp_title();
echo $this->disp_params['block_body_start'];
echo $this->get_layout_start();
echo $this->get_layout_item_start();
echo '<a href="#">(...)</a>';
echo $this->get_layout_item_end();
echo $this->get_layout_end();
echo $this->disp_params['block_body_end'];
echo $this->disp_params['block_end'];
}
}
}
/**
* Display an error message
*
* @param string Message
*/
function display_error_message( $message = NULL )
{
global $Blog;
if( isset( $this->BlockCache ) )
{ // Do NOT cache because this widget has an error which is dispalyed only for collection admin:
$this->BlockCache->abort_collect();
}
if( $message === NULL )
{
$message = 'Unable to display widget '.$this->get_name();
}
echo $this->disp_params['block_start'];
$this->disp_title();
echo $this->disp_params['block_body_start'];
if( check_user_perm( 'blog_admin', 'edit', false, $Blog->ID ) )
{ // Display error only for collection admin:
display_rendering_error( $message, 'span' );
}
echo $this->disp_params['block_body_end'];
echo $this->disp_params['block_end'];
}
/**
* Create new sub-container automatically
*
* @param string Suffix for new sub-container
* @return string|boolean Code of new created sub-container OR FALSE on fail
*/
function create_auto_subcontainer( $name_suffix = '' )
{
if( ! isset( $this->cached_existing_containers ) )
{ // Get existing containers to avoid duplicate error on inserting:
global $DB;
$SQL = new SQL( 'Get existing widget containers before auto create new' );
$SQL->SELECT( 'wico_code' );
$SQL->FROM( 'T_widget__container' );
$SQL->WHERE( 'wico_coll_ID = '.$this->get_coll_ID() );
$SQL->WHERE_and( 'wico_skin_type = '.$DB->quote( $this->get_container_param( 'skin_type' ) ) );
$this->cached_existing_containers = array_map( 'strtolower', $DB->get_col( $SQL ) );
}
// Set data for new creating sub-container:
$new_WidgetContainer = new WidgetContainer();
$new_WidgetContainer->set( 'coll_ID', $this->get_coll_ID() );
$auto_container_name = $this->get_container_param( 'name' ).$name_suffix;
$new_container_name = $auto_container_name;
$auto_container_code = strtolower( preg_replace( '/[^0-9a-z\-]+/i', '_', $new_container_name ) );
if( strlen( $auto_container_code ) > 125 )
{ // Limit widget code to avoid mysql error of long data:
$auto_container_code = substr( $auto_container_code, strlen( $auto_container_code ) - 123 );
}
$new_container_code = $auto_container_code;
$c = 1;
while( in_array( $new_container_code, $this->cached_existing_containers ) )
{ // Find unique container code per collection and skin type:
$new_container_code = $auto_container_code.'_'.$c;
$new_container_name = $auto_container_name.' '.$c;
$c++;
}
$new_WidgetContainer->set( 'code', $new_container_code );
$new_WidgetContainer->set( 'name', utf8_substr( $new_container_name, 0, 128 ) );
$new_WidgetContainer->set( 'skin_type', $this->get_container_param( 'skin_type' ) );
$new_WidgetContainer->set( 'main', 0 );
// Insert new sub-container:
if( ! $new_WidgetContainer->dbinsert() )
{ // Stop updating if some new container cannot be created:
return false;
}
// Cache new created sub-container:
$this->cached_existing_containers[] = $new_container_code;
// Set this temp flag to update widget form with new created sub-container:
$this->reload_page_after_update = true;
return $new_container_code;
}
}
?>