<?php
/**
* This file implements the Short Links plugin for b2evolution
*
* Creates wiki links
*
* 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
* @ignore
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
/**
* @package plugins
*/
class shortlinks_plugin extends Plugin
{
var $code = 'b2evWiLi';
var $name = 'Short Links';
var $priority = 35;
var $version = '7.2.3';
var $group = 'rendering';
var $short_desc;
var $long_desc;
var $help_topic = 'wiki-links-plugin';
var $number_of_installs = 1;
/**
* Init
*/
function PluginInit( & $params )
{
$this->short_desc = T_('Wiki Links converter');
$this->long_desc = T_('You can create links with [[CamelCase]] or ((CamelCase)) which will try to link to the category or the post with the slug "camel-case". See manual for more.');
}
/**
* Define here default custom settings that are to be made available
* in the backoffice for collections, private messages and newsletters.
*
* @param array Associative array of parameters.
* @return array See {@link Plugin::get_custom_setting_definitions()}.
*/
function get_custom_setting_definitions( & $params )
{
return array(
'link_types' => array(
'label' => T_('Link types to allow'),
'type' => 'checklist',
'options' => array(
array( 'absolute_urls', sprintf( $this->T_('Absolute URLs (starting with %s or %s) in brackets'), '<code>http://</code>, <code>https://</code>, <code>mailto://</code>', '<code>//</code>' ), 1 ),
array( 'abs_url_target_blank', $this->T_('Open in new tab').' (<code>target="_blank"</code>)', 1, NULL, NULL, NULL, NULL, NULL, array( 'style' => 'margin-left:20px' ) ),
array( 'abs_url_optimize', $this->T_('If an absolute URL references a collection on this system, try to optimize and keep just the slug '), 1, NULL, NULL, NULL, NULL, NULL, array( 'style' => 'margin-left:20px' ) ),
array( 'relative_urls', sprintf( $this->T_('Relative URLs (starting with %s followed by a letter or digit) in brackets'), '<code>/</code>' ), 0 ),
array( 'rel_url_optimize', $this->T_('If a relative URL references a collection on this system, try to optimize and keep just the slug'), 1, NULL, NULL, NULL, NULL, NULL, array( 'style' => 'margin-left:20px' ) ),
array( 'anchor', sprintf( $this->T_('Current page anchor URLs (starting with %s) in brackets'), '<code>#</code>' ), 1 ),
array( 'cat_slugs', $this->T_('Category slugs in brackets'), 1 ),
array( 'item_slugs', $this->T_('Item slugs in brackets'), 1 ),
array( 'item_id', $this->T_('Item ID in brackets'), 1 ),
array( 'cat_without_brackets', $this->T_('WikiWords without brackets matching category slugs'), 0 ),
array( 'item_without_brackets', $this->T_('WikiWords without brackets matching item slugs'), 0 ),
),
),
);
}
/**
* 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 )
{
$default_values = array(
'default_post_rendering' => 'opt-out'
);
$default_params = array_merge( $params, $default_values );
return parent::get_coll_setting_definitions( $default_params );
}
/**
* Define here default message settings that are to be made available in the backoffice.
*
* @param array Associative array of parameters.
* @return array See {@link Plugin::GetDefaultSettings()}.
*/
function get_msg_setting_definitions( & $params )
{
// set params to allow rendering for messages by default
$default_params = array_merge( $params, array( 'default_msg_rendering' => 'opt-out' ) );
return parent::get_msg_setting_definitions( $default_params );
}
/**
* Define here default email settings that are to be made available in the backoffice.
*
* @param array Associative array of parameters.
* @return array See {@link Plugin::GetDefaultSettings()}.
*/
function get_email_setting_definitions( & $params )
{
// set params to allow rendering for emails by default:
$default_params = array_merge( $params, array( 'default_email_rendering' => 'opt-out' ) );
return parent::get_email_setting_definitions( $default_params );
}
/**
* Define here default shared settings that are to be made available in the backoffice.
*
* @param array Associative array of parameters.
* @return array See {@link Plugin::GetDefaultSettings()}.
*/
function get_shared_setting_definitions( & $params )
{
// set params to allow rendering for shared container widgets by default:
$default_params = array_merge( $params, array( 'default_shared_rendering' => 'opt-out' ) );
return parent::get_shared_setting_definitions( $default_params );
}
/**
* Event handler: Called when displaying an item/post's content as HTML.
*
* This is different from {@link RenderItemAsHtml()}, because it gets called
* on every display (while rendering gets cached).
*
* @param array Associative array of parameters
* @return boolean Have we changed something?
*/
function DisplayItemAsHtml( & $params )
{
$content = & $params['data'];
// Replace the create post links with simple text if current user has no perm to create a post:
$content = replace_outside_code_tags( '#<a[^>]+href="([^"]+)"[^>]+data-function="create_post" data-coll="(\d+)"[^>]*>(.+?)</a>#i', array( $this, 'callback_replace_post_links' ), $content, 'replace_content_callback' );
return true;
}
/**
* Perform rendering
*
* @param array Associative array of parameters
* 'data': the data (by reference). You probably want to modify this.
* 'format': see {@link format_to_output()}. Only 'htmlbody' and 'entityencoded' will arrive here.
* @return boolean true if we can render something for the required output format
*/
function RenderItemAsHtml( & $params )
{
$content = & $params['data'];
// Get collection from given params:
$setting_Blog = $this->get_Blog_from_params( $params );
// Get currently rendering Item:
$this->current_Item = $this->get_Item_from_params( $params );
$this->link_types = $this->get_coll_setting( 'link_types', $setting_Blog );
return $this->render_content( $content );
}
/**
* Perform rendering of Message content
*
* NOTE: Use default coll settings of comments as messages settings
*
* @see Plugin::RenderMessageAsHtml()
*/
function RenderMessageAsHtml( & $params )
{
$content = & $params['data'];
$this->link_types = $this->get_msg_setting( 'link_types' );
return $this->render_content( $content );
}
/**
* Perform rendering of Email content
*
* NOTE: Use default coll settings of comments as messages settings
*
* @see Plugin::RenderEmailAsHtml()
*/
function RenderEmailAsHtml( & $params )
{
$content = & $params['data'];
$this->render_type = 'email';
$this->link_types = $this->get_email_setting( 'link_types' );
return $this->render_content( $content );
}
/**
* Render content of Item, Comment, Message
*
* @todo get rid of global $blog
*
* @param string Content
* @return boolean
*/
function render_content( & $content )
{
global $admin_url, $blog, $evo_charset, $post_ID;
// Add regexp modifier 'u' to work with UTF-8 strings correctly:
$regexp_modifier = ( $evo_charset == 'utf-8' ) ? 'u' : '';
// -------- ABSOLUTE BRACKETED URLS -------- :
if( ! empty( $this->link_types['absolute_urls'] ) )
{ // If it is allowed by plugin setting
$search_urls = '*
( \[\[ | \(\( ) # Lookbehind for (( or [[
( (https?://|mailto://|//)[^<>{}\s\]]+ ) # URL
( \s \.[a-z0-9_\-\.]+ )? # Style classes started and separated with dot (Optional)
( \s _[a-z0-9_\-]+ )? # Link target started with _ (Optional)
( \s [^\n\r]+? )? # Custom link text instead of URL (Optional)
( \]{2,} | \){2,} ) # Lookahead for )) or ]]
*ix'; // x = extended (spaces + comments allowed)
$content = replace_outside_code_and_short_tags( $search_urls, array( $this, 'callback_replace_bracketed_urls' ), $content, 'replace_content', 'preg_callback' );
}
// -------- RELATIVE BRACKETED URLS -------- :
if( ! empty( $this->link_types['relative_urls'] ) )
{ // If it is allowed by plugin setting
$search_urls = '*
( \[\[ | \(\( ) # Lookbehind for (( or [[
( (/)[^/][^<>{}\s\]]+ ) # URL
( \s \.[a-z0-9_\-\.]+ )? # Style classes started and separated with dot (Optional)
( \s _[a-z0-9_\-]+ )? # Link target started with _ (Optional)
( \s [^\n\r]+? )? # Custom link text instead of URL (Optional)
( \]{2,} | \){2,} ) # Lookahead for )) or ]]
*ix'; // x = extended (spaces + comments allowed)
$content = replace_outside_code_and_short_tags( $search_urls, array( $this, 'callback_replace_bracketed_urls' ), $content, 'replace_content', 'preg_callback' );
}
/* QUESTION: fplanque, implementation of this planned? then use make_clickable() - or remove this comment
$ret = preg_replace("#([\n ])aim:([^,< \n\r]+)#i", "\\1<a href=\"aim:goim?screenname=\\2\\3&message=Hello\">\\2\\3</a>", $ret);
$ret = preg_replace("#([\n ])icq:([^,< \n\r]+)#i", "\\1<a href=\"http://wwp.icq.com/scripts/search.dll?to=\\2\\3\">\\2\\3</a>", $ret);
$ret = preg_replace("#([\n ])www\.([a-z0-9\-]+)\.([a-z0-9\-.\~]+)((?:/[^,< \n\r]*)?)#i", "\\1<a href=\"http://www.\\2.\\3\\4\">www.\\2.\\3\\4</a>", $ret);
$ret = preg_replace("#([\n ])([a-z0-9\-_.]+?)@([^,< \n\r]+)#i", "\\1<a href=\"mailto:\\2@\\3\">\\2@\\3</a>", $ret); */
// To use function replace_special_chars()
load_funcs('locales/_charset.funcs.php');
// -------- STANDALONE WIKIWORDS -------- :
if( ! empty( $this->link_types['cat_without_brackets'] ) ||
! empty( $this->link_types['item_without_brackets'] ) )
{ // Create the links from standalone WikiWords
$search_wikiwords = array();
$replace_links = array();
// STANDALONE WIKIWORDS:
$search = '/
(?<= \s | ^ ) # Lookbehind for whitespace
([\p{Lu}]+[\p{Ll}0-9_]+([\p{Lu}]+[\p{L}0-9_]+)+) # WikiWord or WikiWordLong
(?= [\.,:;!\?] \s | \s | $ ) # Lookahead for whitespace or punctuation
/x'.$regexp_modifier; // x = extended (spaces + comments allowed)
if( preg_match_all( $search, $content, $matches, PREG_SET_ORDER ) )
{
// Construct array of wikiwords to look up in post urltitles
$wikiwords = array();
foreach( $matches as $match )
{
// Convert the WikiWord to an urltitle
$WikiWord = $match[0];
$Wiki_Word = preg_replace( '*([^\p{Lu}_])([\p{Lu}])*'.$regexp_modifier, '$1-$2', $WikiWord );
$wiki_word = utf8_strtolower( $Wiki_Word );
// echo '<br />Match: [', $WikiWord, '] -> [', $wiki_word, ']';
$wiki_word = replace_special_chars( $wiki_word );
$wikiwords[ $WikiWord ] = $wiki_word;
}
// Lookup all urltitles at once in DB and preload cache:
if( ! empty( $this->link_types['cat_without_brackets'] ) )
{
$ChapterCache = & get_ChapterCache();
$ChapterCache->load_urlname_array( $wikiwords );
}
if( ! empty( $this->link_types['item_without_brackets'] ) )
{
$ItemCache = & get_ItemCache();
$ItemCache->load_urltitle_array( $wikiwords );
}
// Construct arrays for replacing wikiwords by links:
foreach( $wikiwords as $WikiWord => $wiki_word )
{
// WikiWord
$search_wikiwords[] = '/
(?<= \s | ^ ) # Lookbehind for whitespace or start
(?<! evo_shortlink_broken">)
'.$WikiWord.' # Specific WikiWord to replace
(?= [\.,:;!\?] \s | \s | $ ) # Lookahead for whitespace or end of string
/sx'; // s = dot matches newlines, x = extended (spaces + comments allowed)
// Find matching Item or Chapter:
if( ! empty( $this->link_types['item_without_brackets'] ) &&
( $Item = & $ItemCache->get_by_urltitle( $wiki_word, false, false ) ) )
{ // Replace WikiWord with post permanent link if item is found:
$replace_links[] = '<a href="'.$Item->get_permanent_url().'">'.$Item->get( 'title' ).'</a>';
}
elseif( ! empty( $this->link_types['cat_without_brackets'] ) &&
( $Chapter = & $ChapterCache->get_by_urlname( $wiki_word, false, false ) ) )
{ // Replace WikiWord with category permanent link if Chapter is found:
$replace_links[] = '<a href="'.$Chapter->get_permanent_url().'">'.$Chapter->get( 'name' ).'</a>';
}
else
{ // Replace WikiWord with broken link if Item and Chapter are not found:
$replace_links[] = $this->get_broken_link( $wiki_word, $WikiWord );
}
}
}
// Replace all found standalone words with links:
$content = replace_outside_code_and_short_tags( $search_wikiwords, $replace_links, $content );
}
// -------- BRACKETED WIKIWORDS -------- :
if( ! empty( $this->link_types['anchor'] ) ||
! empty( $this->link_types['cat_slugs'] ) ||
! empty( $this->link_types['item_slugs'] ) ||
! empty( $this->link_types['item_id'] ) )
{ // If it is allowed by plugin settings:
$search_anchor_slug_itemid = ( empty( $this->link_types['anchor'] ) && empty( $this->link_types['cat_slugs'] ) && empty( $this->link_types['item_slugs'] ) ) ?
'([0-9]+) # Only item ID' :
'([\p{L}0-9#]+[\p{L}0-9#_\-]*) # Anything from Wikiword to WikiWordLong';
$search = '/
(?<= \(\( | \[\[ ) # Lookbehind for (( or [[
'.$search_anchor_slug_itemid.'
(?=
( \s .*? )? # Custom link text instead of post or chapter title with optional style classes
( \){2,} | \]{2,} ) # Lookahead for )) or ]]
)
/x'.$regexp_modifier; // x = extended (spaces + comments allowed)
if( preg_match_all( $search, $content, $matches, PREG_SET_ORDER ) )
{
// Construct array of wikiwords to look up in post urltitles
$wikiwords = array();
foreach( $matches as $match )
{
// Convert the WikiWord to an urltitle
$WikiWord = $match[0];
if( preg_match( '/^[\p{Ll}0-9#_\-]+$/'.$regexp_modifier, $WikiWord ) )
{ // This WikiWord already matches a slug format
$Wiki_Word = $WikiWord;
$wiki_word = $Wiki_Word;
}
else
{ // Convert WikiWord to slug format
$Wiki_Word = preg_replace( array( '*([^\p{Lu}#_])([\p{Lu}#])*'.$regexp_modifier, '*([^0-9])([0-9])*'.$regexp_modifier ), '$1-$2', $WikiWord );
$wiki_word = utf8_strtolower( $Wiki_Word );
}
// Remove additional params from $wiki_word, it should be cleared. We keep the params in $WikiWord and parse them below.
$wiki_word = preg_replace( '/^([^#]+)(#.+)?$/i', '$1', $wiki_word );
$wiki_word = replace_special_chars( $wiki_word );
$wikiwords[ $WikiWord ] = $wiki_word;
}
// Lookup all urltitles at once in DB and preload cache:
if( ! empty( $this->link_types['cat_slugs'] ) )
{
$ChapterCache = & get_ChapterCache();
$ChapterCache->load_urlname_array( $wikiwords );
}
if( ! empty( $this->link_types['item_slugs'] ) )
{
$ItemCache = & get_ItemCache();
$ItemCache->load_urltitle_array( $wikiwords );
}
// Replace wikiwords by links:
foreach( $wikiwords as $WikiWord => $wiki_word )
{
// Initialize current wiki word which is used in callback function callback_replace_bracketed_words():
$this->current_WikiWord = $WikiWord;
$this->current_wiki_word = $wiki_word;
// Fix for regexp:
$WikiWord = preg_quote( $WikiWord, '#' );
// [[WikiWord]]
// [[WikiWord text]]
// [[WikiWord .style.classes text]]
// ((WikiWord))
// ((WikiWord text))
// ((WikiWord .style.classes text))
$search_wikiword = '*
( \[\[ | \(\( ) # Lookbehind for (( or [[
'.$WikiWord.' # Specific WikiWord to replace
( \s \.[a-z0-9_\-\.]+ )? # Style classes started and separated with dot (Optional)
( \s _[a-z0-9_\-]+ )? # Link target started with _ (Optional)
( \s .+? )? # Custom link text instead of post/chapter title (Optional)
( \]{2,} | \){2,} ) # Lookahead for )) or ]]
*isx'; // s = dot matches newlines, x = extended (spaces + comments allowed)
$content = replace_outside_code_and_short_tags( $search_wikiword, array( $this, 'callback_replace_bracketed_words' ), $content, 'replace_content', 'preg_callback' );
}
}
}
return true;
}
/**
* Callback function for replace_outside_code_tags to render links like [[http://site.com/page.html .style.classes text]] or ((http://site.com/page.html .style.classes text))
*
* @param array Matches of regexp
* @return string A processed link to the requested URL
*/
function callback_replace_bracketed_urls( $m )
{
if( ! ( $m[1] == '[[' && substr( $m[7], 0, 2 ) == ']]' ) &&
! ( $m[1] == '((' && substr( $m[7], 0, 2 ) == '))' ) )
{ // Wrong pattern, Return original text:
return $m[0];
}
if( strlen( $m[7] ) > 2 )
{ // Fix Custom link text with appending chars ] or ) if it is ended with chars ] or ):
$m[6] .= substr( $m[7], 0, -2 );
}
// Clear custom link text:
$custom_link_text = utf8_trim( $m[6] );
// Clear custom link style classes:
$custom_link_class = utf8_trim( str_replace( '.', ' ', $m[4] ) );
if( $m[3] != '/' && ! empty( $this->link_types['abs_url_target_blank'] ) )
{ // Force target to "_blank" for absolute URLs when it is defined in plugin settings:
$custom_link_target = '_blank';
}
else
{ // Use custom link target:
$custom_link_target = utf8_trim( $m[5] );
}
// Build a link from bracketed URL:
$r = '<a href="'.$m[2].'"';
$r .= empty( $custom_link_class ) ? '' : ' class="'.$custom_link_class.'"';
$r .= empty( $custom_link_target ) ? '' : ' target="'.$custom_link_target.'"';
$r .= '>';
$r .= empty( $custom_link_text ) ? $m[2] : $custom_link_text;
$r .= '</a>';
return $r;
}
/**
* Callback function for replace_outside_code_tags to render links like [[wiki-word .style.classes text]] or ((wiki-word .style.classes text))
*
* @param array Matches of regexp
* @return string A processed link to post/chapter URL OR a suggestion text to create new post from unfound post urltitle
*/
function callback_replace_bracketed_words( $m )
{
global $blog, $evo_charset, $admin_url;
if( ! ( $m[1] == '[[' && substr( $m[5], 0, 2 ) == ']]' ) &&
! ( $m[1] == '((' && substr( $m[5], 0, 2 ) == '))' ) )
{ // Wrong pattern, Return original text:
return $m[0];
}
if( strlen( $m[5] ) > 2 )
{ // Fix Custom link text with appending chars ] or ) if it is ended with chars ] or ):
$m[4] .= substr( $m[5], 0, -2 );
}
$ItemCache = & get_ItemCache();
$ChapterCache = & get_ChapterCache();
// Add regexp modifier 'u' to work with UTF-8 strings correctly:
$regexp_modifier = ( $evo_charset == 'utf-8' ) ? 'u' : '';
// Parse wiki word to find additional param for atrr "id":
$url_params = '';
preg_match( '/^([^#]+)(#(.+))?$/i', $this->current_WikiWord, $WikiWord_match );
if( empty( $WikiWord_match ) )
{
preg_match( '/#(?<=#).*/', $this->current_WikiWord, $WikiWord_match );
$WikiWord_match[1] = isset( $WikiWord_match[0] ) ? $WikiWord_match[0] : null;
$anchor = $WikiWord_match[1];
}
if( isset( $WikiWord_match[3] ) )
{ // wiki word has attr "id"
$url_params .= '#'.$WikiWord_match[3];
}
// Use title of wiki word without attribute part:
$WikiWord = $WikiWord_match[1];
// Find matching Chapter or Item:
$permalink = '';
$link_text = preg_replace( array( '*([^\p{Lu}_])([\p{Lu}])*'.$regexp_modifier, '*([^0-9])([0-9])*'.$regexp_modifier ), '$1 $2', $WikiWord );
$link_text = ucwords( str_replace( '-', ' ', $link_text ) );
if( ! empty( $this->link_types['item_id'] ) && is_numeric( $this->current_wiki_word ) && ( $Item = & $ItemCache->get_by_ID( $this->current_wiki_word, false, false ) ) )
{ // Item is found
$permalink = $Item->get_permanent_url();
$existing_link_text = $Item->get( 'title' );
}
elseif( ! empty( $this->link_types['cat_slugs'] ) && $Chapter = & $ChapterCache->get_by_urlname( $this->current_wiki_word, false, false ) )
{ // Chapter is found
$permalink = $Chapter->get_permanent_url();
$existing_link_text = $Chapter->get( 'name' );
}
elseif( ! empty( $this->link_types['item_slugs'] ) && $Item = & $ItemCache->get_by_urltitle( $this->current_wiki_word, false, false ) )
{ // Item is found
$permalink = $Item->get_permanent_url();
$existing_link_text = $Item->get( 'title' );
}
elseif( ! empty( $this->link_types['anchor'] ) && isset( $anchor ) && ! empty( $this->current_Item ) )
{ // Use Item's URL with anchor:
if( $this->current_Item->get_permalink_type() != 'none' )
{ // Use full permanent URL like 'http://site.com/item-slug#anchor' only for normal Item that have separate single of intro page,
// For Item without permanent URL like "Content Blcok" or "Special" we should use only relative URL like '#anchor' in order
// to link always to currently opened URL, because they are included inside of another Item. NOTE: for proper work skin must not use html tag `<base href="/skin_folder" />`.
$permalink = $this->current_Item->get_permanent_url();
}
$permalink = $url_params == '' ? $permalink.$anchor : $url_params;
$existing_link_text = $this->current_Item->get( 'title' );
unset($anchor);
}
// Clear custom link text:
$custom_link_text = utf8_trim( $m[4] );
// Clear custom link style classes:
$custom_link_class = utf8_trim( str_replace( '.', ' ', $m[2] ) );
// Clear custom link target:
$custom_link_target = utf8_trim( $m[3] );
if( ! empty( $permalink ) )
{ // Chapter or Item are found in DB
$custom_link_class = empty( $custom_link_class ) ? '' : ' class="'.$custom_link_class.'"';
$custom_link_target = empty( $custom_link_target ) ? '' : ' target="'.$custom_link_target.'"';
if( ! empty( $custom_link_text ) )
{ // [[WikiWord custom link text]] or ((WikiWord custom link text)) or [[WikiWord .style.classes custom link text]] or ((WikiWord .style.classes custom link text))
return '<a href="'.$permalink.$url_params.'"'.$custom_link_class.$custom_link_target.'>'.$custom_link_text.'</a>';
}
elseif( $m[1] == '[[' )
{ // [[Wikiword]] or [[Wikiword .style.classes]]
return '<a href="'.$permalink.$url_params.'"'.$custom_link_class.$custom_link_target.'>'.$existing_link_text.'</a>';
}
else
{ // ((Wikiword)) or ((Wikiword .style.classes))
return '<a href="'.$permalink.$url_params.'"'.$custom_link_class.$custom_link_target.'>'.$link_text.'</a>';
}
}
else
{ // Chapter and Item are not found in DB
if( ( empty( $this->link_types['item_id'] ) && is_numeric( $this->current_wiki_word ) ) ||
( empty( $this->link_types['anchor'] ) && isset( $anchor ) ) )
{ // Return original text if no found by numeric wikiword and "Item ID in brackets" is disabled:
return $m[0];
}
else
{ // Display a link to suggest to create new post from wiki word:
return $this->get_broken_link( $this->current_wiki_word, ( empty( $custom_link_text ) ? $link_text : $custom_link_text ), $custom_link_class );
}
}
}
/**
* Get HTML code for broken link
*
* @param string Post slug
* @param string Link/Span text
* @param string Link/Span class
* @return string
*/
function get_broken_link( $post_slug, $text, $class = '' )
{
global $blog, $admin_url, $evo_charset;
if( isset( $this->render_type ) && $this->render_type == 'email' )
{ // Don't render broken link for Email Campaign because it is impossible
// to check user permission when content will be viewed on email inbox:
return $text;
}
// Add regexp modifier 'u' to work with UTF-8 strings correctly:
$regexp_modifier = ( $evo_charset == 'utf-8' ) ? 'u' : '';
$class = empty( $class ) ? '' : $class.' ';
if( is_numeric( $post_slug ) && ! is_numeric( $text ) )
{ // Try to use custom text if it is provided instead of post ID to suggest a link to create new post:
$post_slug = preg_replace( array( '*([^\p{Lu}#_])([\p{Lu}#])*'.$regexp_modifier, '*([^0-9])([0-9])*'.$regexp_modifier ), '$1-$2', utf8_strtolower( $text ) );
}
if( isset( $blog ) && ! is_numeric( $post_slug ) )
{ // Suggest to create new post from given word:
$before_wikiword = '<a'
.' href="#"'
.' class="'.$class.'evo_shortlink_broken"'
// Add these data attributes in order to display this link only for user who can really create a post:
.' data-function="create_post" data-coll="'.$blog.'">';
$after_wikiword = '</a>';
}
else
{ // Don't allow to create new post from numeric wiki word:
$before_wikiword = '<span class="'.$class.'evo_shortlink_broken">';
$after_wikiword = '</span>';
}
return $before_wikiword.$text.$after_wikiword;
}
/**
* Callback function to replace the links for creating new posts if current user has no permission
*
* @param array Matches
* @return string
*/
function callback_replace_post_links( $matches )
{
if( ! isset( $matches[1], $matches[2], $matches[3] ) )
{ // Return a source string when no enough data to check user permissions:
return $matches[0];
}
$BlogCache = & get_BlogCache();
$Blog = & $BlogCache->get_by_ID( $matches[2], false, false );
// Get an URL to create new post,
// If this function return an empty string then current user has no permission:
$new_post_url = $Blog ? $Blog->get_write_item_url( 0, $matches[3] ) : false;
if( ! $new_post_url )
{ // If user has no permission to create a post for the collection,
// display only a link text without providing a link to create new post:
return $matches[3];
}
// If user has a permission to create a post for the collection,
// display the source link but replace the source URL with new generated,
// because it may be different between back- and front-office and also between
// anonymous and logged in users (disp=edit vs disp=anonpost):
return preg_replace( '# href="[^"]+"#i', ' href="'.$new_post_url.'" title="'.format_to_output( T_('Create').'...', 'htmlattr' ).'"', $matches[0] );
}
/**
* Event handler: Called when displaying editor toolbars on post/item form.
*
* This is for post/item edit forms only. Comments, PMs and emails use different events.
*
* @todo dh> This seems to be a lot of Javascript. Please try exporting it in a
* (dynamically created) .js src file. Then we could use cache headers
* to let the browser cache it.
* @param array Associative array of parameters
* @return boolean did we display a toolbar?
*/
function AdminDisplayToolbar( & $params )
{
if( ! empty( $params['Item'] ) )
{ // Item is set, get Blog from post:
$edited_Item = & $params['Item'];
$Collection = $Blog = & $edited_Item->get_Blog();
}
if( empty( $Blog ) )
{ // Item is not set, try global Blog:
global $Collection, $Blog;
if( empty( $Blog ) )
{ // We can't get a Blog, this way "apply_rendering" plugin collection setting is not available:
return false;
}
}
$apply_rendering = $this->get_coll_setting( 'coll_apply_rendering', $Blog );
if( empty( $apply_rendering ) || $apply_rendering == 'never' )
{ // Plugin is not enabled for current case, so don't display a toolbar:
return false;
}
// Print toolbar on screen:
return $this->DisplayCodeToolbar( $Blog, $params );
}
/**
* Event handler: Called when displaying editor toolbars on comment form.
*
* @param array Associative array of parameters
* @return boolean did we display a toolbar?
*/
function DisplayCommentToolbar( & $params )
{
if( ! empty( $params['Comment'] ) )
{ // Comment is set, get Blog from comment:
$Comment = & $params['Comment'];
if( ! empty( $Comment->item_ID ) )
{
$comment_Item = & $Comment->get_Item();
$Collection = $Blog = & $comment_Item->get_Blog();
}
}
if( empty( $Blog ) )
{ // Comment is not set, try global Blog:
global $Collection, $Blog;
if( empty( $Blog ) )
{ // We can't get a Blog, this way "apply_comment_rendering" plugin collection setting is not available:
return false;
}
}
$apply_rendering = $this->get_coll_setting( 'coll_apply_comment_rendering', $Blog );
if( empty( $apply_rendering ) || $apply_rendering == 'never' )
{ // Plugin is not enabled for current case, so don't display a toolbar:
return false;
}
// Print toolbar on screen
return $this->DisplayCodeToolbar( $Blog, $params );
}
/**
* Event handler: Called when displaying editor toolbars for message.
*
* @param array Associative array of parameters
* @return boolean did we display a toolbar?
*/
function DisplayMessageToolbar( & $params )
{
$apply_rendering = $this->get_msg_setting( 'msg_apply_rendering' );
if( ! empty( $apply_rendering ) && $apply_rendering != 'never' )
{ // Print toolbar on screen:
return $this->DisplayCodeToolbar( NULL, $params );
}
return false;
}
/**
* Event handler: Called when displaying editor toolbars for email.
*
* @param array Associative array of parameters
* @return boolean did we display a toolbar?
*/
function DisplayEmailToolbar( & $params )
{
$apply_rendering = $this->get_email_setting( 'email_apply_rendering' );
if( ! empty( $apply_rendering ) && $apply_rendering != 'never' )
{ // Print toolbar on screen:
return $this->DisplayCodeToolbar( NULL, $params );
}
return false;
}
/**
* Display Toolbar
*
* @param object Blog
*/
function DisplayCodeToolbar( $Blog = NULL, $params = array() )
{
global $Hit, $baseurl, $debug;
if( $Hit->is_lynx() )
{ // let's deactivate toolbar on Lynx, because they don't work there:
return false;
}
$params = array_merge( array(
'js_prefix' => '', // Use different prefix if you use several toolbars on one page
), $params );
// Load js to work with textarea:
require_js_defer( 'functions.js', 'blog', true );
// Load js and css for modal window:
$this->require_js_defer( 'shortlinks.js', true );
$this->require_css( 'shortlinks.css', true );
// Initialize JavaScript to build and open window:
echo_modalwindow_js();
// Initialize Javascript to build shortlinks modal window;
$this->init_js_lang_vars();
$js_config = array(
'js_prefix' => $params['js_prefix'],
'plugin_code' => $this->code,
'toolbar_title_before' => format_to_js( $this->get_template( 'toolbar_title_before' ) ),
'toolbar_title_after' => format_to_js( $this->get_template( 'toolbar_title_after' ) ),
'toolbar_group_before' => format_to_js( $this->get_template( 'toolbar_group_before' ) ),
'toolbar_group_after' => format_to_js( $this->get_template( 'toolbar_group_after' ) ),
'toolbar_title' => T_('Short Links:'),
'button_title' => T_('Link to a Post'),
'button_value' => T_('Link to a Post'),
'button_class' => $this->get_template( 'toolbar_button_class' ),
);
if( is_ajax_request() )
{
?>
<script>
jQuery( document ).ready( function() {
window.evo_init_shortlinks_toolbar( <?php echo evo_json_encode( $js_config ); ?> );
} );
</script>
<?php
}
else
{
expose_var_to_js( 'shortlinks_toolbar_'.$params['js_prefix'], $js_config, 'evo_init_shortlinks_toolbar_config' );
}
echo $this->get_template( 'toolbar_before', array( '$toolbar_class$' => $params['js_prefix'].$this->code.'_toolbar' ) );
echo $this->get_template( 'toolbar_after' );
return true;
}
/**
* Initialize JavaScript to build and open window for shortlinks
*/
function init_js_lang_vars( $relative_to = 'rsc_url' )
{
// TODO: Include in rsc/js/src/evo_init_plugin_shortlinks.js
global $Blog;
// Initialize variables for the file "shortlinks.js":
echo '<script>
var shortlinks_coll_urlname = \''.( empty( $Blog ) ? 'undefined' : $Blog->urlname ).'\';
var shortlinks_title_link_to_post = \''.TS_('Link to a Post').get_manual_link( 'shortlinks-plugin-link-post-dialog' ).'\';
var shortlinks_collections = \''.TS_('Collections').'\';
var shortlinks_insert_full_cover_image = \''.TS_('Insert full cover image').'\';
var shortlinks_insert_title = \''.TS_('Insert title').'\';
var shortlinks_insert_thumbnail_of_cover = \''.TS_('Insert thumbnail of cover or first image').'\';
var shortlinks_insert_excerpt = \''.TS_('Insert excerpt').'\';
var shortlinks_insert_teaser = \''.TS_('Insert teaser').'\';
var shortlinks_insert_read_more_link = \''.TS_('Insert "Read more" link').'\';
var shortlinks_slug = \''.TS_('Slug').'\';
var shortlinks_mode = \''.TS_('Mode').'\';
var shortlinks_use_title = \''.TS_('Use title of destination post as link text').'\';
var shortlinks_use_slug_words = \''.TS_('Use slug words as link text').'\';
var shortlinks_classes = \''.TS_('Classes').'\';
var shortlinks_target = \''.TS_('Target').'\';
var shortlinks_none = \''.TS_('None').'\';
var shortlinks_blank = \''.TS_('Blank').'\';
var shortlinks_parent = \''.TS_('Parent').'\';
var shortlinks_top = \''.TS_('Top').'\';
var shortlinks_text = \''.TS_('Text').'\';
var shortlinks_search = \''.TS_('Search').'\';
var shortlinks_clear = \''.TS_('Clear').'\';
var shortlinks_back = \''.TS_('Back').'\';
var shortlinks_insert_short_link = \''.TS_('Insert Short Link').'\';
var shortlinks_insert_with_options = \''.TS_('Insert with options').'...'.'\';
var shortlinks_insert_snippet_link = \''.TS_('Insert Snippet + Link').'...'.'\';
var shortlinks_select_item = \''.TS_('Please select at least one item option to insert.').'\';
var shortlinks_insert_link = \''.TS_('Insert Link').'\';
var shortlinks_read_more = \''.TS_('Read more').'\';
var shortlinks_link_to_post = \''.TS_('Link to a Post').'\';
</script>';
}
/**
* Event handler: called at the beginning of {@link Item::dbinsert() inserting
* an item/post in the database}.
*
* @param array Associative array of parameters
* - 'Item': the related Item (by reference)
*/
function PrependItemInsertTransact( & $params )
{
$Item = & $params['Item'];
// Get collection from given params:
$setting_Blog = $this->get_Blog_from_params( $params );
if( ! $this->is_renderer_enabled( $this->get_coll_setting( 'coll_apply_rendering', $setting_Blog ), $Item->get_renderers_validated() ) )
{ // Don't try to optimize when this plugin is not applied for Items:
return;
}
// Get settings to know what should be optimized:
$this->link_types = $this->get_coll_setting( 'link_types', $setting_Blog );
// Optimize URLs:
$Item->set( 'content', $this->optimize_urls( $Item->get( 'content' ) ) );
}
/**
* Event handler: called at the beginning of {@link Item::dbupdate() updating
* an item/post in the database}.
*
* @param array Associative array of parameters
* - 'Item': the related Item (by reference)
*/
function PrependItemUpdateTransact( & $params )
{
$this->PrependItemInsertTransact( $params );
}
/**
* Event handler: called at the beginning of {@link Comment::dbinsert() inserting
* a Comment in the database}.
*
* @param array Associative array of parameters
* - 'Comment': the related Comment (by reference)
*/
function PrependCommentInsertTransact( & $params )
{
$Comment = & $params['Comment'];
// Get collection from given params:
$setting_Blog = $this->get_Blog_from_params( $params );
if( ! $this->is_renderer_enabled( $this->get_coll_setting( 'coll_apply_comment_rendering', $setting_Blog ), $Comment->get_renderers_validated() ) )
{ // Don't try to optimize when this plugin is not applied for Comments:
return;
}
// Get settings to know what should be optimized:
$this->link_types = $this->get_coll_setting( 'link_types', $setting_Blog );
// Optimize URLs:
$Comment->set( 'content', $this->optimize_urls( $Comment->get( 'content' ) ) );
}
/**
* Event handler: called at the beginning of {@link Comment::dbupdate() updating
* a Comment in the database}.
*
* @param array Associative array of parameters
* - 'Comment': the related Comment (by reference)
*/
function PrependCommentUpdateTransact( & $params )
{
$this->PrependCommentInsertTransact( $params );
}
/**
* Event handler: called at the beginning of {@link Message::dbinsert_discussion() inserting
* an Message in the database}.
*
* @param array Associative array of parameters
* - 'Message': the related Message (by reference)
*/
function PrependMessageInsertTransact( & $params )
{
$Message = & $params['Message'];
if( ! $this->is_renderer_enabled( $this->get_msg_setting( 'msg_apply_rendering' ), $Message->get_renderers_validated() ) )
{ // Don't try to optimize when this plugin is not applied for Items:
return;
}
$this->link_types = $this->get_msg_setting( 'link_types' );
$Message->set( 'text', $this->optimize_urls( $Message->get( 'text' ) ) );
}
/**
* Event handler: called at the beginning of {@link EmailCampaign::dbinsert() inserting
* an Email Campaign in the database}.
*
* @param array Associative array of parameters
* - 'EmailCampaign': the related EmailCampaign (by reference)
*/
function PrependEmailInsertTransact( & $params )
{
$EmailCampaign = & $params['EmailCampaign'];
if( ! $this->is_renderer_enabled( $this->get_email_setting( 'email_apply_rendering' ), $EmailCampaign->get_renderers_validated() ) )
{ // Don't try to optimize when this plugin is not applied for Items:
return;
}
$this->link_types = $this->get_email_setting( 'link_types' );
$EmailCampaign->set( 'email_text', $this->optimize_urls( $EmailCampaign->get( 'email_text' ) ) );
//$EmailCampaign->set( 'email_html', $this->optimize_urls( $EmailCampaign->get( 'email_html' ) ) );
//$EmailCampaign->set( 'email_plaintext', $this->optimize_urls( $EmailCampaign->get( 'email_plaintext' ) ) );
}
/**
* Event handler: called at the beginning of {@link EmailCampaign::dbupdate() updating
* an Email Campaign in the database}.
*
* @param array Associative array of parameters
* - 'EmailCampaign': the related EmailCampaign (by reference)
*/
function PrependEmailUpdateTransact( & $params )
{
$this->PrependEmailInsertTransact( $params );
}
/**
* Optimize URLs in content
*
* @param string Source content
* @return string Optimized content
*/
function optimize_urls( $content )
{
if( ! empty( $this->link_types['abs_url_optimize'] ) )
{ // Optimize absolute URLs:
$content = replace_outside_code_and_short_tags( '*
( \[\[ | \(\( ) # Lookbehind for (( or [[
( ( (https?://|//).+/ ) ( [^/][^<>{}\s\]\)]+ ) ) # URL
( \s.+ )? # Additional attributes like style classes, link target, custon link text (Optional)
( \]{2,} | \){2,} ) # Lookahead for )) or ]]
*ix', // x = extended (spaces + comments allowed)
array( $this, 'optimize_urls_callback' ), $content, 'replace_content', 'preg_callback' );
}
if( ! empty( $this->link_types['rel_url_optimize'] ) )
{ // Optimize relative URLs:
$content = replace_outside_code_and_short_tags( '*
( \[\[ | \(\( ) # Lookbehind for (( or [[
( ( /(.+/)? ) ( [^/][^<>{}\s\]\)]+ ) ) # URL
( \s.+ )? # Additional attributes like style classes, link target, custon link text (Optional)
( \]{2,} | \){2,} ) # Lookahead for )) or ]]
*ix', // x = extended (spaces + comments allowed)
array( $this, 'optimize_urls_callback' ), $content, 'replace_content', 'preg_callback' );
}
return $content;
}
/**
* Callback function for URLs optimization
*
* @param array $m
* @return string
*/
function optimize_urls_callback( $m )
{
if( ! ( $m[1] == '[[' && substr( $m[7], 0, 2 ) == ']]' ) &&
! ( $m[1] == '((' && substr( $m[7], 0, 2 ) == '))' ) )
{ // Wrong pattern, Return original text:
return $m[0];
}
if( preg_match( '#^(https?://|//)$#', $m[4] ) &&
! is_internal_url( $m[3] ) )
{ // This is an absolute URL but this is an external URLs,
// don't optimize this URL to slug:
return $m[0];
}
if( strlen( $m[7] ) > 2 )
{ // Fix Custom link text with appending chars ] or ) if it is ended with chars ] or ):
$m[6] .= substr( $m[7], 0, -2 );
}
$slug = $m[5];
$anchor_position = strpos( $slug, '#' );
if( $anchor_position !== false )
{ // Remove anchor from slug:
$slug = substr( $slug, 0, $anchor_position );
}
$SlugCache = & get_SlugCache();
if( ! $SlugCache->get_by_name( $slug, false, false ) )
{ // The Slug is not found in system, Keep it as is without optimization:
return $m[0];
}
// Return short link tag only with slug(without absolute or relative path):
return $m[1].$m[5].$m[6].$m[7];
}
}
?>