<?php
/**
* Kunena Component
* @package Kunena.Framework
* @subpackage BBCode
*
* @copyright (C) 2008 - 2014 Kunena Team. All rights reserved.
* @license http://www.gnu.org/copyleft/gpl.html GNU/GPL
* @link http://www.kunena.org
**/
defined ( '_JEXEC' ) or die ();
// TODO: add possibility to hide contents from these tags:
// [hide], [confidential], [spoiler], [attachment], [code]
/**
* Kunena BBCode Editor Class
*
* @version 2.0
*/
class KunenaBbcodeEditor {
var $editor_elements = array();
/**
* @param array $config
*/
function __construct($config = array()) {
$this->config = $config;
}
/**
* @param array $config
* @return KunenaBbcodeEditor
*/
public static function getInstance($config = array()) {
static $instance = false;
if (! $instance) {
$instance = new KunenaBbcodeEditor ($config);
}
return $instance;
}
/**
* Inserts a button or another element at the specified location. See insertElements for details.
*
* @param $element
* @param null $pos
* @param $where
* @return bool
*/
public function insertElement ($element, $pos=NULL, $where=NULL) {
if ( is_subclass_of($element, 'KunenaBbcodeEditorElement') ) {
$this->insertElements(array($element), $pos, $where);
}
}
/**
* Inserts a list of buttons or other elements at the specified location. The position $pos can be
* specified as NULL, 'after' or 'before. In the case of 'after' and 'before' the element will be
* inserted after/before the element named $where. If NULL is specified then it is inserted after
* all other elements.
*
* @param $elements
* @param null $pos
* @param $where
* @return bool
*/
public function insertElements ($elements, $pos=NULL, $where=NULL) {
$new_elements_keys = array();
if (!is_array($elements)) {
return false;
}
foreach ( $elements as $v) {
$new_elements_keys[] = $v->name;
}
$new_elements_values = array_values($elements);
$editor_keys = array_keys($this->editor_elements);
$editor_values = array_values($this->editor_elements);
switch ($pos) {
case 'after':
if (($pos = array_search($where, $editor_keys)) === false)
return false;
$pos++;
break;
case 'before':
$pos = array_search($where, $editor_keys);
if ($pos === false)
return false;
break;
default:
$pos = count($editor_keys);
}
array_splice($editor_keys, $pos, 0, $new_elements_keys);
array_splice($editor_values, $pos, 0, $new_elements_values);
$this->editor_elements = array_combine($editor_keys, $editor_values);
}
/**
* Parses an XML description of the buttons into the internal object representation.
*
* @static
* @param SimpleXMLElement $xml
* @return array
*/
public static function parseXML (SimpleXMLElement $xml) {
$elements = array();
foreach ($xml as $xml_item) {
if ($xml_item['config']) {
$cfgVariable = (string) $xml_item['config'];
$cfgValue = intval($cfgVariable[0] != '!');
if (!$cfgValue) $cfgVariable = substr($cfgVariable, 1);
if (KunenaFactory::getConfig()->$cfgVariable != $cfgValue)
continue;
}
$class = "KunenaBbcodeEditor" . strtoupper($xml_item->getName());
$item = call_user_func(array($class, 'parseXML'), $xml_item);
$elements[$item->name] = $item;
}
return $elements;
}
/**
* @param string $identifier
*/
public function initialize($identifier='class') {
$js = "window.addEvent('domready', function() {
kbbcode = new kbbcode('kbbcode-message', 'kbbcode-toolbar', {
dispatchChangeEvent: true,
changeEventDelay: 1000,
interceptTab: true
});\n";
$xml_file = simplexml_load_file(dirname(__FILE__).'/editor.xml');
$this->editor_elements = self::parseXML($xml_file);
//Hook to manipulate the Editor XML like adding buttons
$dispatcher = JDispatcher::getInstance();
JPluginHelper::importPlugin('kunena');
$dispatcher->trigger( 'onKunenaBbcodeEditorInit', array ( $this ) );
foreach ($this->editor_elements as $item) {
$js .= $item->generateJs($identifier);
}
$js .= "});\n";
$template = KunenaTemplate::getInstance();
$template->addScript('js/editor.js');
JFactory::getDocument()->addScriptDeclaration( "// <![CDATA[\n{$js}\n// ]]>");
}
}
/**
* Class KunenaBbcodeEditorElement
*/
abstract class KunenaBbcodeEditorElement {
var $name;
/**
* Constructor for the base class for editor elements.
*
* @param $name
*/
function __construct($name) {
$this->name = $name;
}
/**
* Generats and creates the JavaScript code required to show the buttons.
*
* @abstract
* @param $identifier
*/
abstract function generateJs ($identifier);
/**
* Internal function that is used to parse an XML representation of an element.
*
* @static
* @abstract
* @param $xml
*/
public static function parseXML (SimpleXMLElement $xml) {}
}
/**
* Class KunenaBbcodeEditorButton
*/
class KunenaBbcodeEditorButton extends KunenaBbcodeEditorElement {
protected $tag;
protected $config;
protected $title;
protected $alt;
protected $class;
protected $actions = array();
/**
* Create a button that can be added to the BBCode Editor.
*
* @param $name
* @param $class
* @param $tag
* @param $title
* @param $alt
*/
function __construct($name, $class, $tag, $title, $alt) {
parent::__construct($name);
$this->tag = $tag;
$this->title = $title;
$this->alt = $alt;
$this->class = $class;
}
public static function parseXML(SimpleXMLElement $xml) {
$obj = new KunenaBbcodeEditorButton((string)$xml['name'], (string)$xml['class'], (string)$xml['tag'], (string)$xml['title'], (string)$xml['alt']);
foreach ($xml as $xml_item) {
$item = array();
$item['type'] = $xml_item->getName();
$item['tag'] = (string)$xml_item['tag'];
if ($xml_item['disabled'] == 'disabled')
continue;
if ($xml_item['config']) {
$cfgVariable = (string) $xml_item['config'];
$cfgValue = intval($cfgVariable[0] != '!');
if (!$cfgValue) $cfgVariable = substr($cfgVariable, 1);
if (KunenaFactory::getConfig()->$cfgVariable != $cfgValue)
continue;
}
switch ($item['type']) {
case 'wrap-selection':
$item['empty_before'] = (string)$xml_item['empty_before'];
$item['empty_after'] = (string)$xml_item['empty_after'];
$item['repeat'] = (string)$xml_item['repeat'];
$item['start'] = (string)$xml_item['start'];
$item['end'] = (string)$xml_item['end'];
$item['before'] = (string)$xml_item['before'];
$item['after'] = (string)$xml_item['after'];
break;
case 'display':
$item['selection'] = (string)$xml_item['selection'];
$item['class'] = (string)$xml_item['class'];
break;
case 'link':
$item['url'] = (string)$xml_item['url'];
break;
}
$obj->actions[] = $item;
}
return $obj;
}
/**
* Generate the JavaScript for each of the actions that the button has.
*
* @param $name
* @return string
*/
protected function editorActionJs($name) {
$js = '';
foreach ($this->actions as $action) {
$tag = $action['tag'] ? $action['tag'] : $this->tag;
switch ($action['type']) {
case 'display':
// <display name="kbbcode-color-options" />
if (!$tag) continue;
if ($action['selection']) {
$js .= "\n sel = this.focus().getSelection(); if (sel) { document.id('{$action['selection']}').set('value', sel); }";
}
$js .= "\n kToggleOrSwap('kbbcode-{$name}-options');";
break;
case 'wrap-selection':
// <wrap-selection />
if (!$tag) continue;
if (!$action['repeat']) {
$js .= "\n this.focus().wrapSelection('[{$tag}]', '[/{$tag}]', true);";
} else {
$start = $action['start'] ? $action['start'] : "[{$action['tag']}]";
$end = $action['end'] ? $action['end'] : "[/{$action['tag']}]";
$js .= "\nselection = this.focus().getSelection();
if (selection) {
this.processEachLine(function(line) {
return ' {$start}' + line + '{$end}';
}, false);
this.wrapSelection('{$action['before']}', '{$action['after']}', false);
} else {
this.wrapSelection('{$action['empty_before']}', '{$action['empty_after']}', false);
}";
}
break;
case 'link':
// <link url="http://docs.kunena.org/index.php/bbcode" />
$js .= "\n window.open('{$action['url']}');";
break;
}
}
return $js;
}
function generateJs ($identifier) {
// <button tag="i" name="italic" title="COM_KUNENA_EDITOR_ITALIC" alt="COM_KUNENA_EDITOR_HELPLINE_ITALIC">
$name = $this->name ? $this->name : ($this->tag ? $this->tag : '#');
$class = $this->class ? $this->class : "kbbcode-{$name}-button";
$js = "\nkbbcode.addFunction('{$name}', function() {";
$js .= $this->editorActionJs($name);
$js .= "\n}, {";
foreach (array('title', 'alt') as $type) {
if ($this->$type) {
$value = JText::_($this->$type, true);
$js .= "\n '{$type}': '{$value}',";
}
}
$js .= "\n '{$identifier}': '{$class}'";
$js .= "\n});\n";
return $js;
}
/**
* Add a new display action. This can be used to show a button specific action area.
*
* @param $selection
* @param $class
* @param null $tag
*/
function addDisplayAction ($selection, $class, $tag=NULL) {
$item['type'] = 'display';
$item['selection'] = $selection;
$item['class'] = $class;
$item['tag'] = $tag;
$this->actions[] = $item;
}
/**
* Specify what code should be inserted when the user presses the button.
*
* @param null $repeat
* @param null $empty_before
* @param null $empty_after
* @param null $start
* @param null $end
* @param null $before
* @param null $after
* @param null $tag
*/
function addWrapSelectionAction ($repeat=NULL, $empty_before=NULL, $empty_after=NULL, $start=NULL, $end=NULL, $before=NULL, $after=NULL, $tag=NULL) {
$item['type'] = 'wrap-selection';
$item['repeat'] = $repeat;
if ($repeat) {
$item['empty_before'] = $empty_before;
$item['empty_after'] = $empty_after;
$item['start'] = $start;
$item['end'] = $end;
$item['before'] = $before;
$item['after'] = $after;
}
$item['tag'] = $tag;
$this->actions[] = $item;
}
/**
* Open the specified URL when the button is pressed.
*
* @param $url
*/
function addUrlAction ($url) {
$item['type'] = 'url';
$item['url'] = $url;
$this->actions[] = $item;
}
}
/**
* Class KunenaBbcodeEditorSeparator
*/
class KunenaBbcodeEditorSeparator extends KunenaBbcodeEditorElement {
public function generateJs ($identifier) {
$js = "\nkbbcode.addFunction('#', function() {";
$js .= "\n}, {";
$js .= "\n 'class': 'kbbcode-separator'";
$js .= "});\n";
return $js;
}
public static function parseXML (SimpleXMLElement $xml) {
return new KunenaBbcodeEditorSeparator((string)$xml['name']);
}
}