<?php
/**
* This file implements the Automatic Links plugin for b2evolution
*
* b2evolution - {@link http://b2evolution.net/}
* Released under GNU GPL License - {@link http://b2evolution.net/about/gnu-gpl-license}
* @copyright (c)2003-2020 by Francois Planque - {@link http://fplanque.com/}
*
* @package plugins
*/
if( !defined('EVO_MAIN_INIT') ) die( 'Please, do not access this page directly.' );
/**
* Automatic links plugin.
*
* @todo dh> Provide a setting for: fp> This should be a DIFFERENT plugin that kicks in last in the rendering and actually prcesses ALL links, auto links as well as explicit/manual links
* - marking external and internal (relative URL or on the blog's URL) links with a HTML/CSS class
* - add e.g. 'target="_blank"' to external links
* @todo Add "max. displayed length setting" and add full title + dots in the middle to shorten it.
* (e.g. plain long URLs with a lot of params and such). This should not cause the layout to
* behave ugly. This should only shorten non-whitespace strings in the link's innerHTML of course.
*
* @package plugins
*/
class autolinks_plugin extends Plugin
{
var $code = 'b2evALnk';
var $name = 'Auto Links';
var $priority = 63;
var $version = '7.2.3';
var $group = 'rendering';
var $short_desc;
var $long_desc;
var $help_topic = 'autolinks-plugin';
var $number_of_installs = null; // Let admins install several instances with potentially different word lists
/**
* Lazy loaded from txt files
*
* @var array of array for each blog. Index 0 is for shared content
*/
var $link_array = array();
var $already_linked_array;
/**
* Previous word from the text during the make clickable process
*
* @var string
*/
var $previous_word = null;
/**
* Previous word in lower case format
*
* @var string
*/
var $previous_lword = null;
/**
* Shows if the previous word was already used/converted to a link
*
* @var boolean
*/
var $previous_used = false;
var $already_linked_usernames;
/**
* Array of tags used for current collection
*
* @var array
*/
var $tags_array = NULL;
var $already_linked_tags = NULL;
/**
* Current Blog
*
* @var object
*/
var $current_Blog = NULL;
/**
* Init
*/
function PluginInit( & $params )
{
$this->short_desc = T_('Make URLs and specific terms/defintions clickable');
$this->long_desc = T_('This renderer automatically creates links for you. URLs can be made clickable automatically. Specific and frequently used terms can be configured to be automatically linked to a definition URL.');
}
/**
* Define the GLOBAL settings of the plugin here. These can then be edited in the backoffice in System > Plugins.
*
* @param array Associative array of parameters (since v1.9).
* 'for_editing': true, if the settings get queried for editing;
* false, if they get queried for instantiating {@link Plugin::$Settings}.
* @return array see {@link Plugin::GetDefaultSettings()}.
* The array to be returned should define the names of the settings as keys (max length is 30 chars)
* and assign an array with the following keys to them (only 'label' is required):
*/
function GetDefaultSettings( & $params )
{
return array(
'autolink' => array(
'label' => T_('Create auto-links for'),
'type' => 'checklist',
'options' => array(
array( 'defs_default', sprintf( T_('Definitions as defined in %s'), '<code>definitions.default.txt</code>' ), 1 ),
array( 'defs_local', sprintf( T_('Definitions as defined in %s'), '<code>definitions.local.txt</code>' ), 0 ),
)
),
'autolink_defs_db' => array(
'label' => T_('Custom definitions'),
'type' => 'html_textarea',
'rows' => 15,
'note' => $this->T_( 'Enter custom definitions above.' ),
'defaultvalue' => '',
),
);
}
/**
* 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(
'autolink_urls' => array(
'label' => $this->T_('Autolink URLs'),
'type' => 'checkbox',
'note' => sprintf( $this->T_('Find URLs that match %s, %s or %s'), '<code>http://*</code>', '<code>https://*</code>', '<code>www.*.*</code>' ),
'defaultvalue' => 1,
),
'autolink_emails' => array(
'label' => $this->T_('Autolink email addresses'),
'type' => 'checkbox',
'note' => sprintf( $this->T_('Find addresses that match %s or %s'), '<code>mailto:</code>', '<code>*@*.*</code>' ),
'defaultvalue' => 1,
),
'autolink_username' => array(
'label' => T_( 'Autolink usernames' ),
'type' => 'checkbox',
// TRANS: the user can type in any username after "@" but it's typically only lowercase letters and no spaces.
'note' => $this->T_( '@username will link to the user profile page' ),
'defaultvalue' => 0,
),
'autolink_defs_coll_db' => array(
'label' => T_( 'Custom autolink definitions' ),
'type' => 'html_textarea',
'rows' => 15,
'note' => $this->T_( 'Enter custom definitions above.' ),
'defaultvalue' => '',
),
);
}
/**
* 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::GetDefaultSettings()}.
*/
function get_coll_setting_definitions( & $params )
{
$default_values = array(
'autolink_tag' => 0,
'autolink_post_nofollow_auto' => 0,
'autolink_comment_nofollow_auto' => 0,
);
// set params to allow rendering for comments by default
$default_params = array_merge( $params, array( 'default_comment_rendering' => 'stealth' ) );
$coll_params = parent::get_coll_setting_definitions( $default_params );
if( isset( $coll_params['autolink_defs_coll_db'] ) )
{ // Store this param in order to put under "Autolink tags":
$coll_param_autolink_defs_coll_db = $coll_params['autolink_defs_coll_db'];
unset( $coll_params['autolink_defs_coll_db'] );
}
$coll_params['autolink_tag'] = array(
'label' => T_('Autolink tags'),
'type' => 'checkbox',
'note' => $this->T_( 'Find text matching tags of the current collection and autolink them to the tag page in the current collection' ),
'defaultvalue' => $default_values['autolink_tag'],
);
if( isset( $coll_param_autolink_defs_coll_db ) )
{ // Put the setting under "Autolink tags":
$coll_params['autolink_defs_coll_db'] = $coll_param_autolink_defs_coll_db;
}
return array_merge( $coll_params,
array(
// No follow in posts
'autolink_post_nofollow' => array(
'label' => T_('No follow in posts'),
'type' => 'checklist',
'options' => array(
array( 'auto', $this->T_('Add rel="nofollow" to links from autolink definitions'), $default_values['autolink_post_nofollow_auto'] ),
)
),
// No follow in comments
'autolink_comment_nofollow' => array(
'label' => T_('No follow in comments'),
'type' => 'checklist',
'options' => array(
array( 'auto', $this->T_('Add rel="nofollow" to links from autolink definitions'), $default_values['autolink_comment_nofollow_auto'] ),
)
),
)
);
}
/**
* 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' => 'stealth' ) );
return array_merge( parent::get_msg_setting_definitions( $default_params ),
array(
// No follow settings in messages:
'autolink_nofollow' => array(
'label' => T_('No follow in messages'),
'type' => 'checklist',
'options' => array(
array( 'auto', $this->T_('Add rel="nofollow" to auto-links'), 0 ),
)
),
)
);
}
/**
* 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' => 'stealth' ) );
return array_merge( parent::get_email_setting_definitions( $default_params ),
array(
// No follow settings in email campaigns:
'autolink_nofollow' => array(
'label' => T_('No follow in messages'),
'type' => 'checklist',
'options' => array(
array( 'auto', $this->T_('Add rel="nofollow" to auto-links'), 0 ),
)
),
)
);
}
/**
* 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' => 'stealth' ) );
return array_merge( parent::get_shared_setting_definitions( $default_params ),
array(
// No follow settings in messages:
'autolink_nofollow' => array(
'label' => T_('No follow in messages'),
'type' => 'checklist',
'options' => array(
array( 'auto', $this->T_('Add rel="nofollow" to auto-links'), 0 ),
)
),
)
);
}
/**
* Lazy load global definitions array
*
* @param object Blog
*/
function load_link_array( $Blog )
{
global $plugins_path;
if( !isset($this->link_array[0]) )
{ // global defs NOT already loaded
$this->link_array[0] = array();
if( $this->get_checklist_setting( 'autolink', 'defs_default' ) )
{ // Load defaults:
$this->read_csv_file( $plugins_path.'autolinks_plugin/definitions.default.txt', 0 );
}
if( $this->get_checklist_setting( 'autolink', 'defs_local' ) )
{ // Load local user defintions:
$this->read_csv_file( $plugins_path.'autolinks_plugin/definitions.local.txt', 0 );
}
$text = $this->Settings->get( 'autolink_defs_db', 0 );
if( !empty($text) )
{ // Load local user defintions:
$this->read_textfield( $text, 0 );
}
}
// load defs for current blog:
$coll_ID = !empty( $Blog ) ? $Blog->ID : 0;
if( !isset($this->link_array[$coll_ID]) )
{ // This blog is not loaded yet:
$this->link_array[$coll_ID] = array();
}
$text = $this->setting_autolink_defs_coll_db;
if( ! empty( $text ) )
{ // Load local user defintions:
$this->read_textfield( $text, $coll_ID );
}
// Prepare working link array:
$this->replacement_link_array = array_merge( $this->link_array[0], $this->link_array[$coll_ID] );
}
/**
* Load contents of one specific CSV file
*
* @param string $filename
*/
function read_csv_file( $filename, $coll_ID )
{
if( ! $handle = @fopen( $filename, 'r') )
{ // File could not be opened:
return;
}
while( ($data = fgetcsv($handle, 1000, ';', '"')) !== false )
{
$this->read_line( $data, $coll_ID );
}
fclose($handle);
}
/**
* Load contents of one large textfield to be treated as CSV
*
* Note: This method is probably not well suited for very large lists.
*
* @param string $filename
*/
function read_textfield( $text, $coll_ID )
{
// split into lines:
$lines = preg_split( '#\r|\n#', $text );
foreach( $lines as $line )
{
// CSV style decoding in memory:
// $keywords = preg_split( "/[\s,]*\\\"([^\\\"]+)\\\"[\s,]*|[\s,]+/", "textline with, commas and \"quoted text\" inserted", 0, PREG_SPLIT_DELIM_CAPTURE );
$data = explode( ';', $line );
$this->read_line( $data, $coll_ID );
}
}
/**
* read line
*
* @param exploded $data array
*/
function read_line( $data, $coll_ID )
{
if( empty( $data[0] ) )
{ // Skip empty and comment lines
return;
}
$word = $data[0];
$url = isset( $data[3] ) ? $data[3] : NULL;
if( $url == '-' || empty( $url ) )
{ // Remove URL (useful to remove some defs on a specific site):
unset( $this->link_array[0][$word] );
unset( $this->link_array[$coll_ID][$word] );
}
else
{
if( ! isset( $this->link_array[ $coll_ID ][ $word ] ) )
{ // Initialize array only first time to store several previous words for each word:
$this->link_array[ $coll_ID ][ $word ] = array();
}
$this->link_array[ $coll_ID ][ $word ][ $data[1] ] = $url;
}
}
/**
* Perform rendering
*
* @param array Associative array of parameters
* (Output format, see {@link format_to_output()})
* @return boolean true if we can render something for the required output format
*/
function RenderItemAsHtml( & $params )
{
$content = & $params['data'];
// Get collection from given params (also it is used to build link for tag links):
$this->current_Blog = $this->get_Blog_from_params( $params );
// Define the setting names depending on what is rendering now
if( !empty( $params['Comment'] ) )
{ // Comment is rendering
$this->setting_nofollow_auto = $this->get_checklist_setting( 'autolink_comment_nofollow', 'auto', 'coll', $this->current_Blog );
}
else
{ // Item is rendering
$this->setting_nofollow_auto = $this->get_checklist_setting( 'autolink_post_nofollow', 'auto', 'coll', $this->current_Blog );
}
$this->setting_autolink_defs_coll_db = $this->get_coll_setting( 'autolink_defs_coll_db', $this->current_Blog );
$this->setting_autolink_urls = $this->get_coll_setting( 'autolink_urls', $this->current_Blog );
$this->setting_autolink_emails = $this->get_coll_setting( 'autolink_emails', $this->current_Blog );
$this->setting_autolink_username = $this->get_coll_setting( 'autolink_username', $this->current_Blog );
$this->setting_autolink_tag = $this->get_coll_setting( 'autolink_tag', $this->current_Blog );
return $this->render_content( $content, $this->current_Blog );
}
/**
* Perform rendering of Message content
*
* NOTE: Use default coll settings of comments as messages settings
*
* @see Plugin::RenderMessageAsHtml()
*/
function RenderMessageAsHtml( & $params )
{
$content = & $params['data'];
// Message is rendering
$this->setting_nofollow_auto = $this->get_checklist_setting( 'autolink_nofollow', 'auto', 'msg' );
$this->setting_autolink_defs_coll_db = $this->get_msg_setting( 'autolink_defs_coll_db' );
$this->setting_autolink_urls = $this->get_msg_setting( 'autolink_urls' );
$this->setting_autolink_emails = $this->get_msg_setting( 'autolink_emails' );
$this->setting_autolink_username = $this->get_msg_setting( 'autolink_username' );
$this->setting_autolink_tag = false;
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'];
// Email is rendering
$this->setting_nofollow_auto = $this->get_checklist_setting( 'autolink_nofollow', 'auto', 'email' );
$this->setting_autolink_defs_coll_db = $this->get_email_setting( 'autolink_defs_coll_db' );
$this->setting_autolink_urls = $this->get_email_setting( 'autolink_urls' );
$this->setting_autolink_emails = $this->get_email_setting( 'autolink_emails' );
$this->setting_autolink_username = $this->get_email_setting( 'autolink_username' );
$this->setting_autolink_tag = false;
return $this->render_content( $content );
}
/**
* Render content of Item, Comment, Message
*
* @param string Content
* @param object Blog
* return boolean
*/
function render_content( & $content, $item_Blog = NULL )
{
// Reset already linked usernames:
$this->already_linked_usernames = array();
// Load global defs:
$this->load_link_array( $item_Blog );
// Load all tags of current collection:
$this->load_tags_array( $item_Blog, $content );
// Reset already linked tags:
$this->already_linked_tags = array();
// reset already linked:
$this->already_linked_array = array();
if( preg_match_all( '|[\'"](http://[^\'"]+)|i', $content, $matches ) )
{ // There are existing links:
$this->already_linked_array = $matches[1];
}
if( $this->setting_autolink_urls )
{ // Make the URLs clickable:
$content = make_clickable( $content, '&', array( $this, 'make_clickable_callback_urls' ), '', true );
}
if( $this->setting_autolink_emails )
{ // Make the email addresses clickable:
$content = make_clickable( $content, '&', array( $this, 'make_clickable_callback_emails' ), '', true );
}
// Make the desired remaining terms/definitions, usernames or tags clickable:
$content = make_clickable( $content, '&', array( $this, 'make_clickable_callback_definitions' ), '', true );
return true;
}
function FilterCommentContent( & $params )
{
$Comment = & $params['Comment'];
$comment_Item = & $Comment->get_Item();
$item_Blog = & $comment_Item->get_Blog();
if( in_array( $this->code, $Comment->get_renderers_validated() ) )
{ // Always allow rendering for comment
$render_params = array_merge( array( 'data' => & $Comment->content, 'Item' => & $comment_Item ), $params );
$this->RenderItemAsHtml( $render_params );
}
return false;
}
/**
* Callback function to make URLs clickable
*
* @param string Text
* @param string Url delimeter
* @param string Additional attributes for tag <a>
* @return string The clickable text.
*/
function make_clickable_callback_urls( $text, $moredelim = '&', $additional_attrs = '' )
{
if( ! empty( $additional_attrs ) )
{
$additional_attrs = ' '.trim( $additional_attrs );
}
// Add style class to break long urls:
$additional_attrs = stripos( $additional_attrs, ' class="' ) === false
? $additional_attrs.' class="linebreak"'
: preg_replace( '/ class="([^"]*)"/i', ' class="$1 linebreak"', $additional_attrs );
$pattern_domain = '([\p{L}0-9\-]+\.[\p{L}0-9\-.\~]+)'; // a domain name (not very strict)
$text = preg_replace(
/* Tblue> I removed the double quotes from the first RegExp because
it made URLs in tag attributes clickable.
See http://forums.b2evolution.net/viewtopic.php?p=92073 */
array( '#(^|[\s>\(]|\[url=)(https?)://([^"<>{}\s]+[^".,:;!\?<>{}\s\]\)])#i',
'#(^|[\s>\(]|\[url=)www\.'.$pattern_domain.'([^"<>{}\s]*[^".,:;!\?\s\]\)])#i' ),
array( '$1<a href="$2://$3"'.$additional_attrs.'>$2://$3</a>',
'$1<a href="http://www.$2$3$4"'.$additional_attrs.'>www.$2$3$4</a>' ),
$text );
return $text;
}
/**
* Callback function to make email addresses clickable
*
* @param string Text
* @param string Url delimeter
* @param string Additional attributes for tag <a>
* @return string The clickable text.
*/
function make_clickable_callback_emails( $text, $moredelim = '&', $additional_attrs = '' )
{
if( ! empty( $additional_attrs ) )
{
$additional_attrs = ' '.trim( $additional_attrs );
}
$pattern_domain = '([\p{L}0-9\-]+\.[\p{L}0-9\-.\~]+)'; // a domain name (not very strict)
$text = preg_replace(
/* Tblue> I removed the double quotes from the first RegExp because
it made URLs in tag attributes clickable.
See http://forums.b2evolution.net/viewtopic.php?p=92073 */
array( '#(^|[\s>\(]|\[url=)mailto://([^"<>{}\s]+[^".,:;!\?<>{}\s\]\)])#i',
'#(^|[\s>\(]|\[url=)([a-z0-9\-_.]+?)@'.$pattern_domain.'([^".,:;!\?&<\s\]\)]+)#i' ),
array( '$1<a href="mailto://$2"'.$additional_attrs.'>mailto://$2</a>',
'$1<a href="mailto:$2@$3$4"'.$additional_attrs.'>$2@$3$4</a>' ),
$text );
return $text;
}
/**
* Callback function to make terms/definitions, usernames or tags clickable
*
* @param string Text
* @param string Url delimeter
* @return string The clickable text.
*/
function make_clickable_callback_definitions( $text, $moredelim = '&' )
{
global $evo_charset;
if( ! empty( $this->replacement_link_array ) )
{ // Make the desired remaining terms/definitions clickable:
$regexp_modifier = '';
if( $evo_charset == 'utf-8' )
{ // Add this modifier to work with UTF-8 strings correctly
$regexp_modifier = 'u';
}
// Previous word in lower case format
$this->previous_lword = null;
// Previous word was already used/converted to a link
$this->previous_used = false;
// Optimization: Check if the text contains words from the replacement links strings, and call replace callback only if there is at least one word which needs to be replaced.
$text_words = preg_split( '/\s/', utf8_strtolower( $text ) );
foreach( $text_words as $text_word )
{ // Trim the signs [({/ from start and the signs ])}/.,:;!? from end of each word
$clear_word = preg_replace( '#^[\[\({/]?([@\p{L}0-9_\-]{3,})[\.,:;!\?\]\)}/]?$#i', '$1', $text_word );
if( $clear_word != $text_word )
{ // Append a clear word to array if word has the punctuation signs
$text_words[] = $clear_word;
}
}
// Check if a content has at least one definition to make an url from word
$text_contains_replacement = ( count( array_intersect( $text_words, array_keys( $this->replacement_link_array ) ) ) > 0 );
if( $text_contains_replacement )
{ // Find word with 3 characters at least:
$text = preg_replace_callback( '#(^|\s|[(),;\'\"\[{/])([@\p{L}0-9_\-\.]{3,})([\.,:;!\'\"\?\]\)}/]?)#i'.$regexp_modifier, array( & $this, 'replace_callback' ), $text );
}
// Cleanup words to be deleted:
$text = preg_replace( '/[@\p{L}0-9_\-]+\s*==!#DEL#!==/i'.$regexp_modifier, '', $text );
}
// Replace @usernames with user identity link:
$text = replace_outside_code_and_short_tags( '#@([a-z0-9_.\-]+)#i', '@', $text, array( $this, 'replace_usernames' ) );
// Make tag names clickable:
$text = $this->replace_tags( $text );
return $text;
}
/**
* This is the 2nd level of callback!!
*
* @param array The matches of regexp:
* 1 => punctuation signs before word
* 2 => a clear word without punctuation signs
* 3 => punctuation signs after word
*/
function replace_callback( $matches )
{
global $disp, $Item;
$link_attrs = '';
if( $this->setting_nofollow_auto )
{ // Add attribute rel="nofollow" for auto-links:
$link_attrs .= ' rel="nofollow"';
}
$before_word = $matches[1];
$word = $matches[2];
$after_word = $matches[3];
if( substr( $word, -1 ) == '.' )
{ // If word has a dot in the end
$word = substr( $word, 0, -1 );
$after_word = '.'.$after_word;
}
$lword = utf8_strtolower( $word );
$r = $before_word.$word.$after_word;
if( isset( $this->replacement_link_array[ $lword ] ) )
{ // There is an autolink definition with the current word
if( ! empty( $this->previous_lword ) && isset( $this->replacement_link_array[ $lword ][ $this->previous_lword ] ) )
{ // Set an previous word and url from config array:
// An optional previous required word (allows to create groups of 2 words)
$previous = $this->previous_lword;
// Url for current word
$url = $this->replacement_link_array[ $lword ][ $this->previous_lword ];
}
else
{ // No previous word, it is a single word
foreach( $this->replacement_link_array[ $lword ] as $previous => $url )
{ // Initialize an optional previous required word and url as first of the current word
break;
}
}
if( ! preg_match( '#(^|[a-z]+:)//#', $url ) )
{ // Use default URL scheme if it is not defined by config:
$url = 'http://'.$url;
}
if( in_array( $url, $this->already_linked_array ) || in_array( $lword, $this->already_linked_usernames ) )
{ // Do not repeat link to same destination:
// pre_dump( 'already linked:'. $url );
// save previous word in original and lower case format with the after word signs
$this->previous_word = $word.$after_word;
$this->previous_lword = $lword.$after_word;
$this->previous_used = false;
return $r;
}
if( ( $disp == 'single' || $disp == 'page' ) &&
isset( $Item ) &&
$Item->get_permanent_url() == $url )
{ // Do not make a link to same permalink URL of the current Item:
return $r;
}
if( !empty( $previous ) )
{ // This definitions is a group of two word separated with space
if( $this->previous_used || ( $this->previous_lword != $previous ) )
{ // We do not have the required previous word or it was already used to another autolink definition
// pre_dump( 'previous word does not match', $this->previous_lword, $previous );
// save previous word in original and lower case format with the after word signs
$this->previous_word = $word.$after_word;
$this->previous_lword = $lword.$after_word;
$this->previous_used = false;
return $r;
}
$r = '==!#DEL#!==<a href="'.$url.'"'.$link_attrs.'>'.$this->previous_word.' '.$word.'</a>'.$after_word;
}
else
{ // Single word
$r = $before_word.'<a href="'.$url.'"'.$link_attrs.'>'.$word.'</a>'.$after_word;
}
// Make sure we don't link to same destination twice in the same text/post:
$this->already_linked_array[] = $url;
// Mark that the previous word was already converted to a link
$this->previous_used = true;
}
else
{ // Mark that the previous word was NOT converted to a link
$this->previous_used = false;
}
// save previous word in original and lower case format with the after word signs
// Note: after_word signs are important to be saved because in case of autlink definitions with two words the first word must have exact matching at the end!
$this->previous_word = $word.$after_word;
$this->previous_lword = $lword.$after_word;
return $r;
}
/**
* Replace @usernames with link to profile page
*
* @param string Content
* @param array Search list
* @param array Replace list
* @return string Content
*/
function replace_usernames( $content, $search_list, $replace_list )
{
if( empty( $this->setting_autolink_username ) )
{ // No data to correct username linking, Exit here:
return $content;
}
if( preg_match_all( $search_list, $content, $user_matches ) )
{
// Add this for rel attribute in order to activate bubbletips on usernames
$link_attrs = ' rel="bubbletip_user_%user_ID%"';
$link_attrs .= ' class="user"';
if( !empty( $user_matches[1] ) )
{
$UserCache = & get_UserCache();
foreach( $user_matches[1] as $u => $username )
{
if( in_array( $username, $this->already_linked_usernames ) )
{ // Skip this username, it was already linked before
continue;
}
$username_source = $user_matches[0][ $u ];
if( ! ( $User = & $UserCache->get_by_login( $username ) ) )
{ // If user is not found try to find by removing a dot at the end,
// because usernames may contain dot and also we have a case like "some text to @username.",
// so firstly we find user by 'username.' and then by 'username':
$username_without_dot = rtrim( $username, '.' );
if( $username_without_dot != $username &&
( $User = & $UserCache->get_by_login( $username_without_dot ) ) )
{ // We found user by username without dots at the end:
$username = $username_without_dot;
$username_source = rtrim( $username_source, '.' );
// Check again but already for username without dot:
if( in_array( $username, $this->already_linked_usernames ) )
{ // Skip this username, it was already linked before:
continue;
}
}
}
if( $User )
{ // Replace @usernames
$user_link_attrs = str_replace( '%user_ID%', $User->ID, $link_attrs );
$user_link = '<a href="'.$User->get_userpage_url( $this->current_Blog->ID ).'"'.$user_link_attrs.'>'.$username_source.'</a>';
$content = preg_replace( '#'.preg_quote( $username_source, '#' ).'#', $user_link, $content, 1 );
$this->already_linked_usernames[] = $username;
}
}
}
}
return $content;
}
/**
* Load tags array of collection
*
* @param object Blog
*/
function load_tags_array( $Blog )
{
if( empty( $this->setting_autolink_tag ) )
{ // Don't load tags because it is not required by current settings:
return;
}
if( is_array( $this->tags_array ) )
{ // The tags array is already initialized, Don't do this twice, Exit here:
return;
}
// Get all tags from published posts of the requested collection:
$coll_tags = get_tags( $Blog->ID );
$this->tags_array = array();
foreach( $coll_tags as $coll_tag )
{
$this->tags_array[] = $coll_tag->tag_name;
}
}
/**
* Replace tag names with link to filter posts by the tag
*
* @param string Content
* @return string Content
*/
function replace_tags( $content )
{
if( empty( $this->setting_autolink_tag ) || empty( $this->tags_array ) || empty( $this->current_Blog ) )
{ // No data to correct tag linking, Exit here:
return $content;
}
$new_linked_tags = array();
$tag_search_patterns = array();
$tag_replace_strings = array();
foreach( $this->tags_array as $tag_name )
{
if( in_array( $tag_name, $this->already_linked_tags ) )
{ // Skip this tag because it has been already linked before:
continue;
}
if( stristr( $content, $tag_name ) !== false )
{ // Replace tag name with its link if it is found in text:
$tag_search_patterns[] = '#(^|\W)('.preg_quote( $tag_name, '#' ).')(\W|$)#iu';
$tag_replace_strings[] = '$1'.$this->current_Blog->get_tag_link( $tag_name, "$2" ).'$3';
// Mark this tag as linked and don't link it twice:
$new_linked_tags[] = $tag_name;
}
}
if( count( $tag_search_patterns ) )
{ // Do replacement if at least one tag is found in content:
if( stristr( $content, '<a' ) !== false )
{ // Don't link tags in body of already existing links:
$content = callback_on_non_matching_blocks( $content, '~<a[^>]*>.*?</a>~is',
'replace_content', array( $tag_search_patterns, $tag_replace_strings, 'preg', 1 ) );
}
else
{ // Replace in whole content:
$content = replace_content( $content, $tag_search_patterns, $tag_replace_strings, 'preg', 1 );
}
}
foreach( $new_linked_tags as $n => $new_linked_tag )
{
if( stristr( $content, $new_linked_tag.'</a>' ) === false )
{ // This tag was not linked really in this call, Skip it:
// It may happens when one tag is a substring of other tag, Example:
// - First tag is "long tag"
// - Second tag is "test long tag name"
// - $text = "1 test long tag name 2"
// So we should not mark the second as linked because only the first tag is linked only first time,
// and we should keep the second tag for other strings.
unset( $new_linked_tags[ $n ] );
}
}
if( count( $new_linked_tags ) )
{ // Append new linked tags to skip them in next times:
$this->already_linked_tags = array_merge( $this->already_linked_tags, $new_linked_tags );
}
return $content;
}
}
?>