| Current Path : /home/annegardxb/www/so-assurances/plugins/content/readlesstext/ |
| Current File : /home/annegardxb/www/so-assurances/plugins/content/readlesstext/readlesstextmain.php |
<?php
/**
* @package readlesstext
* @copyright 2008-2014 Parvus
* @license http://www.gnu.org/licenses/gpl-3.0.html
* @link http://joomlacode.org/gf/project/cutoff/
* @author Parvus
*
* readless is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* readless is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with readless. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @version $Id$
*/
defined( '_JEXEC' ) or die;
require_once 'readlesstextcache.php';
require_once 'readlesstextexpand.php';
require_once 'readlesstexthelper.php';
require_once 'readlesstextthumb.php';
if ( !/*NOT*/ class_exists( 'ContentHelperRoute' ) )
{
/* There have been some reports of people that somehow got errors by having
* this class not being included by default (not only with this plugin; also
* with other completely unrelated plugins.
* Although this block should never be entered, it serves as a workaround
* for those people having this issue.
*/
require_once( JPATH_SITE . '/components/com_content/helpers/route.php' );
}
class ReadLessTextMain extends JPlugin
{
/**
* Constructor
* @param object &$subject The object to observe
* @param array $config An optional associative array of configuration settings.
*/
public function __construct( &$subject, $config = array() )
{
parent::__construct( $subject, $config );
}
/**
* Main entry function. The configuration parameters set in the constructor are used to fully exert read less text's
* functionality.
* @param JTableContent $article The item/article being prepared for display.
* @param number $callCount The number of times this function has been
* called by the same plugin. This value is used to determine whether
* the given article may be shortened.
* Will have been incremented by one when this function returns.
* @param string $pluginName
*/
public function ReadLessText( &$article, &$callCount, $pluginName = 'plg_content_readlesstext', $cacheTable = '#__readlesstext' )
{
// jimport( 'joomla.error.profiler' );
// $this->profiler = new JProfiler();
// $this->profiles = array();
// $this->profiles[] = $this->profiler->mark( ' IN ' . $article->id );
JPlugin::loadLanguage( $pluginName );
/* The two blocks below are a workaround for issues in the Joomla core.
* - In a 'featured' view, the fulltext is not set or empty, even when it
* is present in the database
* - In a single article view, the readmore property is not set.
* Seen in 1.6.6, 1.7.2 & 2.5.4
*
* It also ensures that when this plugin is invoked on other components (e.g. com_media),
* There are no php notices.
*/
foreach ( array( 'fulltext', 'readmore' ) as $var )
{
if ( !/*NOT*/ isset( $article->$var ) )
{
$article->$var = '';
}
}
if ( isset( $article->id ) and $article->readmore and ( !/*NOT*/ $article->fulltext ) )
{
$db = JFactory::getDBO();
$query = "SELECT c.fulltext
FROM #__content c
WHERE c.id = " . $article->id;
$db->setQuery( $query );
$article->fulltext = $db->loadResult();
}
$this->_isShortened = false;
$this->_prefix = '';
$this->_suffix = '';
$alwaysActiveForGuests = $this->params->get( 'alwaysActiveForGuests', '0' );
if ( $alwaysActiveForGuests )
{
$extraDiscoverInfo = 'Guests can only see the shortened article, unless a context in the <em>Disallowed</em> field matches this item.';
$activeByDefaultOnAllContentItems = ( JFactory::getUser()->guest == 1 );
}
else
{
$extraDiscoverInfo = '';
$activeByDefaultOnAllContentItems = false;
}
// $this->profiles[] = $this->profiler->mark( ' before filter ' );
$options = array();
if ( ReadLessTextHelper::Filter( $callCount, $article, $this->params, $options,
$activeByDefaultOnAllContentItems, $pluginName, $extraDiscoverInfo ) )
{
/* Entering this block means: read less text must be active. */
// $this->profiles[] = $this->profiler->mark( ' after filter ' );
if ( $options[ 'discover' ] )
{
/* Discover mode is active: the article's text has been replaced with
* discover information and may not be altered.
* No need to update the private variables - they will not be used.
*/
}
else
{
// $this->profiles[] = $this->profiler->mark( ' before prepare ' );
$length = $this->_PrepareArticleText( $article, $this->params, $cacheTable );
// $this->profiles[] = $this->profiler->mark( ' after prepare ' );
$imageHtml = '';
$prefix = '';
$shortened = $article->text;
$inlineSuffix = '';
$closingTags = '';
$suffix = '';
$expand = new ReadLessTextExpand();
$expand->SetExpandables( $article, $this->_plaintext, Null, Null );
$this->_GetParams( $article, $length, $expand );
if ( ( $this->_applyFormatting == 'when_active' )
or ( ( $this->_applyFormatting == 'when_long_enough' ) and $length ) )
{
// $this->profiles[] = $this->profiler->mark( ' before strip ' );
$article->text = $this->_StripTokens( $article->text );
// $this->profiles[] = $this->profiler->mark( ' after strip ' );
// $this->profiles[] = $this->profiler->mark( ' before cutoff ' );
$a = $this->_CutOff( $article->text, $length );
$shortened = $a[0];
$closingTags = $a[1];
// $this->profiles[] = $this->profiler->mark( ' after cutoff ' );
}
if ( ( $this->_createThumbnail == 'when_active' )
or ( ( $this->_createThumbnail == 'when_shortened' ) and $this->_isShortened ) )
{
// $this->profiles[] = $this->profiler->mark( ' before thumbnail ' );
$imageHtml = $this->_GetThumbnailHtml( $article );
// $this->profiles[] = $this->profiler->mark( ' after thumbnail ' );
}
if ( ( $this->_addPrefix == 'when_active' )
or ( ( $this->_addPrefix == 'when_shortened' ) and $this->_isShortened ) )
{
$prefix = $this->_prefix;
}
if ( ( $this->_addInlineSuffix == 'when_active' )
or ( ( $this->_addInlineSuffix == 'when_shortened' ) and $this->_isShortened ) )
{
$inlineSuffix = $this->_inlineSuffix;
}
if ( ( $this->_addSuffix == 'when_active' )
or ( ( $this->_addSuffix == 'when_shortened' ) and $this->_isShortened ) )
{
$suffix = $this->_suffix;
}
if ( $this->_cache )
{
$this->_cache->Store();
}
$article->text = $this->_wrapperTag[ 'open' ] . $imageHtml . $prefix
. $shortened
. $inlineSuffix . $closingTags . $suffix . $this->_wrapperTag[ 'close' ];
}
$article->introtext = $article->text;
$article->fulltext = '';
// $this->profiles[] = $this->profiler->mark( ' OUT ' . $article->id );
// $article->text .= '<div>';
// foreach ( $this->profiles as $profile )
// {
// $article->text .= "<br/>" . $profile;
// }
// $article->text .= '<br/></div>';
// $article->introtext = $article->text;
// $article->fulltext = '';
}
}
/**
* Determines the html version of the intro and the full field,
* irrespective the component.
* @param JTableContent $article The item/article being prepared for display.
* @return An array, with the first element containing the intro text,
* if any, and the second element the remainder of the text.
*/
private static function _DetermineArticleText( $article )
{
/* Be extra careful, so that other components do not give PHP notices
* and warnings.
* - Some components do not have the introtext and/or the fulltext field
* but only the text field,
* e.g. ??
* - Others have none of the three, but use a special name (why o why),
* e.g. in com_eventlist:
* datdescription for an event.
* catdescription for a category.
* locdescription for a venue.
* description for a group.
* - whereas others have all three of them, and full info is to be
* fetched from introtext and fulltext.
*/
$introtext = '';
$fulltext = '';
if ( isset( $article->introtext ) )
{
$introtext = $article->introtext;
}
if ( isset( $article->fulltext ) )
{
$fulltext = $article->fulltext;
}
if ( !/*NOT*/ $introtext and !/*NOT*/ $fulltext)
{
if ( isset( $article->text ) and $article->text )
{
$fulltext = $article->text;
}
}
if ( !/*NOT*/ $introtext and !/*NOT*/ $fulltext)
{
/* No text has been found yet. Check if it is stored in a variable named
* xxxdescription, like eventlist does.
*/
$values = array();
foreach ( get_object_vars( $article ) as $key => $value )
{
if ( strstr( $key, 'description') !== false )
{
$values[] = $value;
}
}
/* Only accept the text from a xxxdescription when it is the only one
* found: e.g. eventlist uses one object to store the text of both
* the event and the venue, and it is impossible to choose the correct
* one.
*/
if ( count( $values ) == 1 )
{
$fulltext = $values[0];
}
}
$readmoreStartPos = JString::strpos( $introtext, '<p class="readmore">' );
if ( $readmoreStartPos !== false )
{
/* Another plugin has already tampered with this article, and somehow a readmore token got included.
* e.g. NoNumber's ArticleAnywhere can cause this.
* Consider only the part before that token as the intro text.
*/
$introtext = JString::substr_replace( $introtext, '', $readmoreStartPos );
}
return array( $introtext, $fulltext );
}
/**
* Determines the length of @c $this->_htmltext in the given unit.
* The length is fetched from the cache. If it is not available in the cache, it is calculated and then added to the
* cache - but not yet committed in the cache, use @c Store() for that.
* @param string $lengthUnit One of 'char', 'word', 'sentence', 'paragraph'
* @return int the requested length, in number of @c lengthUnit's
*/
private function _DetermineLength( $string, $lengthUnit )
{
$length = $this->_cache->Get( $lengthUnit );
if ( !/*NOT*/ $length )
{
$end = true; /* Just to define it; it is used as output parameter in the next call, but ignored by us. */
$length = ReadLessTextHelper::DetermineLength( $this->_htmltext, $lengthUnit, $end );
if ( $length )
{
$this->_cache->Set( $lengthUnit, $length );
}
}
return $length;
}
/**
* Determines the correct text to operate on. Sets the private variables plaintext and htmltext.
* @param JTableContent $article IN OUT. The item/article being prepared for display. The properties introtext, text,
* fulltext and readmore will have been set or adapted.
* @param params $params the parameters to use
* @return mixed boolean false to indicate article may not be shortened, or a number indicating the nr of units to
* retain. It is still possible this number is greater than the entire article text length.
* @post @c $article->text contains the text to be shortened or to display.
* @note Even when @c false is returned, @c $article->text may have been changed - a manually inserted read more
* request is then overridden.
*/
private function _PrepareArticleText( &$article, $params, $cacheTable )
{
$a = self::_DetermineArticleText( $article );
$introtext = $a[0];
$fulltext = $a[1];
$plainintrotext = strip_tags( $introtext );
$plainfulltext = strip_tags( $fulltext );
$this->_plaintext = $plainintrotext . ' ' . $plainfulltext;
$this->_htmltext = $introtext . ' ' . $fulltext;
/* It is tempting to execute the line below, but this yields wrong results.
* There are Russian (e.g. Р 0xd0 0xa0) and Greek (e.g. Π 0xce 0xa0) multi-byte characters
* that are stripped from the 0xa0 byte, giving a rubbish character.
* $this->_htmltext will - after further adaptation - eventually be output,
* so all characters must at all times remain intact.
* - btw, at the very least, the u modifier should be used. See e.g. ReadLessTextHelper::Rtrim()
*/
//$this->_htmltext = preg_replace( '/\s+/muis', ' ', $this->_htmltext )
$this->_cache = new ReadLessTextCache( $cacheTable, JRequest::getWord( 'option' ), $article->id,
'v5.2 (r274)' . md5( $this->_htmltext ) );
$this->_wordCount = $this->_DetermineLength( $this->_htmltext, 'word' );
$this->_lengthUnit = $params->get( 'lengthUnit', 'char' );
/* True when a 'read more' link has been explicitly inserted, and fulltext is not empty.
* - the full text is made by combining introtext, (closing the paragraph), a
* hr tag (id 'system-readmore') (opening the paragraph) and fulltext.
*/
$readmoreMarkerPresent = ( $fulltext and isset( $article->readmore ) and $article->readmore );
if ( $readmoreMarkerPresent and ( $this->_respectExistingReadmoreLink == 'respectShowIntro' ) )
{
/* Shorten the article's intro text. */
$article->text = $introtext;
$cutOffLength = $params->get( 'cutOffTextLength', 1 );
}
else if ( $readmoreMarkerPresent and $this->_respectExistingReadmoreLink ) /* 1 / true */
{
/* Use the article's full intro text; do not try to shorten it. This is
* accomplished by requesting to cut off at a ridiculously high value.
*/
$article->text = $introtext;
$this->_isShortened = true;
$cutOffLength = PHP_INT_MAX;
}
else /* No read more present, or 0 / false */
{
$minimumLength = $params->get( 'minimumTextLength', 1 );
$cutOffLength = $params->get( 'cutOffTextLength', 1 );
$length = $this->_DetermineLength( $this->_htmltext, $this->_lengthUnit );
if ( $length < $minimumLength )
{
$cutOffLength = false;
}
else if ( $length <= $cutOffLength )
{
$cutOffLength = false;
}
else
{
/* Shorten the article's full text. */
$article->text = $this->_htmltext;
}
}
$article->readmore = 0; /* We do not want Joomla to insert his own 'read more' token */
if ( !/*NOT*/ $cutOffLength )
{
/* The article may not be shortened. Ensure the full article is present,
* even when a manual to-be-ignored read more is present.
*/
$article->text = $this->_htmltext;
}
$this->_wrapperTag = array( 'open' => '', 'close' => '' );
$wrapperTag = $params->get( 'wrapperTag', '' );
if ( $wrapperTag )
{
$wrapperClass = $params->get( 'wrapperClass', '' );
if ( $wrapperClass )
{
$this->_wrapperTag[ 'open' ] = '<' . $wrapperTag . ' class="' . $wrapperClass . '">';
}
else
{
$this->_wrapperTag[ 'open' ] = '<' . $wrapperTag . '>';
}
$this->_wrapperTag[ 'close' ] = '</' . $wrapperTag . '>';
}
return $cutOffLength;
}
/**
* Determines various options, which are stored in the private variables.
* @note Not all private variables will be set. Those are covered in the @c _PrepareArticleText function
* @param JTableContent $article RO. The item/article being prepared for display.
* @params mixed $length Boolean false or a positive number. Indicates whether it is already determined if the article
* is to be shortened and how long the shortened article must become.
* @param class $expand Instance of ReadLessTextExpand.
* @pre _PrepareArticleText() must have been called beforehand: e.g. @c _plaintext is used (while expanding).
*/
private function _GetParams( &$article, $length, $expand )
{
$this->_applyFormatting = $this->params->get( 'applyFormatting', 'when_active' );
$this->_addPrefix = $this->params->get( 'addPrefix', 'when_active' );
$this->_addInlineSuffix = $this->params->get( 'addInlineSuffix', 'when_active' );
if ( $this->_addInlineSuffix == 'when_active_use_article_manager_option' )
{
if ( JComponentHelper::getParams( 'com_content' )->get( 'show_readmore' ) )
{
$this->_addInlineSuffix = 'when_active';
}
else
{
$this->_addInlineSuffix = 'no';
}
}
$this->_addSuffix = $this->params->get( 'addSuffix', 'when_active' );
if ( $this->_addSuffix == 'when_active_use_article_manager_option' )
{
if ( JComponentHelper::getParams( 'com_content' )->get( 'show_readmore' ) )
{
$this->_addSuffix = 'when_active';
}
else
{
$this->_addSuffix = 'no';
}
}
$this->_respectExistingReadmoreLink = $this->params->get( 'respectExistingReadmoreLink', true );
if ( $this->_respectExistingReadmoreLink == 'respectShowIntro')
{
if ( JComponentHelper::getParams( 'com_content' )->get( 'show_intro' ) )
{
$this->_respectExistingReadmoreLink = false;
}
else
{
/* The user does _not_ want to resepect an existing 'read more' token,
* and likes to have the intro text separated from the full text after it.
* Only shorten the intro text
*/
$this->_respectExistingReadmoreLink = 'shortenIntroOnly';
}
}
/* An array of tags or tokens which are used later on. */
if ( ( $this->_applyFormatting == 'when_active' )
or ( ( $this->_applyFormatting == 'when_long_enough' ) and $length ) )
{
$list = array(
'extraSelfClosingTags',
'tagsToRemove',
'tagsToRemoveWithContents',
'squareTokensToRemove',
'curlyTokensToRemove',
'squareTokensToRemoveWithContents',
'curlyTokensToRemoveWithContents' );
}
else
{
$list = array(
'extraSelfClosingTags');
}
foreach ( $list as $parameter )
{
$string = JString::strtolower( $this->params->get( $parameter, '' ) );
$string = JString::str_ireplace( ' ', '', $string );
if ( $string )
{
$parameter = '_' . $parameter;
$this->$parameter = explode( ',', $string );
}
}
$this->_articleUrl = JRoute::_( ContentHelperRoute::getArticleRoute(
ReadLessTextHelper::GetArticleSlug( $article ), ReadLessTextHelper::GetCategorySlug( $article ) ) );
$expand->SetExpandables( Null, Null, Null, array( '{url}' => $this->_articleUrl, '{words}' => $this->_wordCount ) );
/* prefix */
$translateAdditions = $this->params->get( 'translateAdditions', false );
$prefixLinksToFullArticle = false;
if ( JFactory::getUser()->guest == 0 )
{
$this->_prefix = $this->params->get( 'userPrefix', '' );
$prefixLinksToFullArticle = $this->params->get( 'userPrefixLinksToFullArticle', true );
}
else
{
$this->_prefix = $this->params->get( 'guestPrefix', '' );
$prefixLinksToFullArticle = $this->params->get( 'guestPrefixLinksToFullArticle', true);
}
if ( $translateAdditions )
{
$this->_prefix = JText::_( $this->_prefix );
}
$expand->SetExpandables( Null, Null, $this->params->get( 'prefixDateFormat', '%m/%d' ), null );
$this->_prefix = $expand->expand( $this->_prefix );
if ( $prefixLinksToFullArticle and $this->_prefix )
{
$this->_prefix = '<a href="' . $this->_articleUrl . '">' . $this->_prefix . '</a>';
}
/* inline suffix */
$inlineSuffixLinksToFullArticle = false;
if ( JFactory::getUser()->guest == 0 )
{
$this->_inlineSuffix = $this->params->get( 'userInlineSuffix', '' );
$inlineSuffixLinksToFullArticle = $this->params->get( 'userInlineSuffixLinksToFullArticle', true );
}
else
{
$this->_inlineSuffix = $this->params->get( 'guestInlineSuffix', '' );
$inlineSuffixLinksToFullArticle = $this->params->get( 'guestInlineSuffixLinksToFullArticle', true );
}
if ( $translateAdditions )
{
$this->_inlineSuffix = JText::_( $this->_inlineSuffix );
}
$expand->SetExpandables( Null, Null, $this->params->get( 'suffixDateFormat', '%m/%d' ), null );
$this->_inlineSuffix = $expand->expand( $this->_inlineSuffix );
if ( $inlineSuffixLinksToFullArticle and $this->_inlineSuffix )
{
$this->_inlineSuffix = '<a href="' . $this->_articleUrl . '">' . $this->_inlineSuffix . '</a>';
}
/* suffix */
$suffixLinksToFullArticle = false;
if ( JFactory::getUser()->guest == 0 )
{
$this->_suffix = $this->params->get( 'userSuffix', '' );
$suffixLinksToFullArticle = $this->params->get( 'userSuffixLinksToFullArticle', true );
}
else
{
$this->_suffix = $this->params->get( 'guestSuffix', '' );
$suffixLinksToFullArticle = $this->params->get( 'guestSuffixLinksToFullArticle', true );
}
if ( $translateAdditions )
{
$this->_suffix = JText::_( $this->_suffix );
}
$expand->SetExpandables( Null, Null, $this->params->get( 'suffixDateFormat', '%m/%d' ), null );
$this->_suffix = $expand->expand( $this->_suffix );
if ( $suffixLinksToFullArticle and $this->_suffix )
{
$this->_suffix = '<a href="' . $this->_articleUrl . '">' . $this->_suffix . '</a>';
}
/* Use the title or one of the -fixes as thumbnail tooltip. */
switch ( $this->params->get( 'thumbnailTitle', 'articleTitle' ) )
{
case '0':
$this->_thumbnailTitle = '';
case 'prefix':
$this->_thumbnailTitle = $this->_prefix;
break;
case 'suffix':
$this->_thumbnailTitle = $this->_suffix;
break;
case 'inlineSuffix':
$this->_thumbnailTitle = $this->_inlineSuffix;
break;
case 'articleTitle':
default:
$this->_thumbnailTitle = $article->title;
break;
}
$this->_thumbnailTitle = strip_tags( $this->_thumbnailTitle );
$this->_createThumbnail = $this->params->get( 'createThumbnail', 'when_active' );
$this->_linkThumbnail = $this->params->get( 'linkThumbnail', true );
$this->_defaultThumbnail = $this->params->get( 'defaultThumbnailTemplate', '' );
$this->_defaultThumbnail = $expand->expand( $this->_defaultThumbnail );
$this->_retainWholeWords = $this->params->get( 'retainWholeWords', false );
$this->_crop[ 'horizontal_position' ] = $this->params->get( 'cropHorizontalPosition', 'no' );
$this->_crop[ 'vertical_position' ] = $this->params->get( 'cropVerticalPosition', 'no' );
$this->_cacheTime = $this->params->get( 'thumbCacheTime', 2419200 /* 4 weeks */ );
$this->_maxImageLoadTime = $this->params->get( 'maxImageLoadTime', 0 );
if ( $this->_maxImageLoadTime <= 0 )
{
$this->_maxImageLoadTime = 9999; /* Ridicously high: clamped below. */
}
$this->_maxImageLoadTime = max( 1, min( 11 /* seconds */, $this->_maxImageLoadTime ) );
$this->_thumbWidth = $this->params->get( 'thumbWidth', 0 );
$this->_thumbHeight = $this->params->get( 'thumbHeight', 0 );
$this->_minimum[ 'width' ] = $this->params->get( 'minimumImageWidth', 0 );
$this->_minimum[ 'height' ] = $this->params->get( 'minimumImageHeight', 0 );
$this->_minimum[ 'ratio' ] = max( 0.05, min( 0.95, $this->params->get( 'minimumImageRatio', 0 ) ) );
}
/**
* Searches for an image that passes all constraints set in the configuration settings; removes that image from the
* article's text - if present - and returns HTML code containing a thumbnail to that image, readily suitable for
* display.
* @param JTableContent $article IN OUT. The item/article being prepared for display.
* @param string $articleText IN, OUT. $article->text may have been adapted when this function returns.
* @return String. The HTML code for displaying the styled, resized and linked first image according to the
* configuration settings, the empty string otherwise.
*/
private function _GetThumbnailHtml( $article )
{
/* - First try to get all data from the cached information. If this succeeds, we can avoid using regular expressions.
* - Else, try to find one in the full unshortened article.
* - Else, try to find one in a possibly referenced gallery.
* - Else, try to find a default thumbnail.
*/
$thumbnailUrl = $this->_GetCachedThumbnail( $article->text );
if ( !/*NOT*/ $thumbnailUrl )
{
$thumbnailUrl = $this->_PopImage( $this->_htmltext );
if ( !/*NOT*/ $thumbnailUrl )
{
$thumbnailUrl = $this->_PopGalleryImage( $this->_htmltext );
if ( !/*NOT*/ $thumbnailUrl )
{
$thumbnailUrl = $this->_GetDefaultThumbnail();
}
}
}
if ( $thumbnailUrl )
{
$attributes = $this->_GetImageAttributes( $this->_thumbnailTitle, $this->_thumbWidth, $this->_thumbHeight );
if ( $this->_linkThumbnail )
{
$thumbnailHtml = '<a href="' . $this->_articleUrl . '"><img src="' . $thumbnailUrl. '"' . $attributes . '/></a>';
}
else
{
$thumbnailHtml = '<img src="' . $thumbnailUrl. '"' . $attributes . '/>';
}
}
else
{
$thumbnailHtml = false;
}
return $thumbnailHtml;
}
/**
* Fetches all cached image information. If the cached information is valid, it is used to construct the HTML code
* containing a thumbnail, readily suitable for display. The image used to create the thumbnail, if present in the
* article, will be stripped from the article's text.
* @param string $articleText OUT. the string to adapt.
* @return String. The HTML code for displaying the styled, resized and
* linked image according to the configuration settings, the empty
* string otherwise.
*/
private function _GetCachedThumbnail( &$articleText )
{
$isValid = false;
if ( $this->_cache )
{
$imageTagStartPos = $this->_cache->Get( 'image_tag_start_pos' );
$imageTagLength = $this->_cache->Get( 'image_tag_length' );
$imageUrl = $this->_cache->Get( 'image_url' );
$thumbnailUrl = $this->_cache->Get( 'thumbnail_url' );
if ( $thumbnailUrl )
{
$isValid = ReadLessTextThumb::ValidateThumbnail( $imageUrl, $thumbnailUrl, $this->_minimum, $this->_crop,
$this->_thumbWidth, $this->_thumbHeight );
}
}
if ( $isValid )
{
/* Check if the thumbnail was derived from an image referenced in the article text
* i.e., check if it is not a default thumbnail.
*/
if ( ( $imageTagStartPos >= 0 ) and ( $imageTagLength > 0 ) )
{
/* Remove the code for the image on the old location.
* Don't do this blindly: it can be (it is likely, as I think it is a often used option)
* that all images already have been removed from the given text.
*/
if ( ( JString::substr( $articleText, $imageTagStartPos, 1 ) == '<' )
and ( JString::substr( $articleText, $imageTagStartPos + $imageTagLength, 1 ) == '>' ) )
{
$imageUrlStartPos = strpos( $articleText, $imageUrl );
if ( ( $imageTagStartPos < $imageUrlStartPos )
and ( $imageUrlStartPos < $imageTagStartPos + $imageTagLength ) )
{
/* Ok, the portion we want to strip down does contain the url, and
* does start and end with an opening resp. closing tag.
* That's all we do to ascertain we will remove the correct portion.
*/
$articleText = JString::substr_replace( $articleText, '', $imageTagStartPos, $imageTagLength );
}
}
}
}
else
{
$thumbnailUrl = false;
}
return $thumbnailUrl;
}
/**
* Searches for an image according to the default thumbnail template, and
* returns HTML code containing a thumbnail to that image, readily
* suitable for display.
* @return String. The HTML code for displaying the styled, resized and
* linked image according to the configuration settings, the empty
* string otherwise.
*/
private function _GetDefaultThumbnail()
{
if ( $this->_defaultThumbnail )
{
/* Be more lenient with respect to the default thumbnail: ensure it does not get rejected due to size and
* dimension constraints.
* The minimum contraints are not used any more after this (so no need to restore them afterwards).
*/
$minimum[ 'width' ] = 0;
$minimum[ 'height' ] = 0;
$minimum[ 'ratio' ] = 0;
$thumbnailUrl = ReadLessTextThumb::GetThumbnail( $this->_defaultThumbnail, $minimum, $this->_crop,
$this->_thumbWidth, $this->_thumbHeight, $this->_cacheTime );
if ( $thumbnailUrl )
{
$this->_cache->Set( 'image_tag_start_pos', -1 );
$this->_cache->Set( 'image_tag_length', 0 );
$this->_cache->Set( 'image_url', $this->_defaultThumbnail );
$this->_cache->Set( 'thumbnail_url', $thumbnailUrl );
}
}
else
{
$thumbnailUrl = false;
}
return $thumbnailUrl;
}
/**
* Searches for an image in the article's text which is big enough according
* to the configuration settings; removes that image from the article's
* text, and returns HTML code containing a thumbnail to that image, readily
* suitable for display.
* @param string $articleText IN, OUT. the string to search in and to adapt.
* @return String. The HTML code for displaying the styled, resized and
* linked first image according to the configuration settings, the empty
* string otherwise.
* @note When an image is found, the results will have been cached when this function returns.
*/
private function _PopImage( &$articleText )
{
$thumbnailUrl = false;
$matchCount = preg_match( self::_imgPattern, $articleText, $matches, PREG_OFFSET_CAPTURE, 0 );
while ( $matchCount )
{
$imageCode = $matches[0][0];
$imageUrl = $matches[1][0];
/* No acceptable image has been found yet, try this one. */
$thumbnailUrl = ReadLessTextThumb::GetThumbnail( $imageUrl, $this->_minimum, $this->_crop, $this->_thumbWidth,
$this->_thumbHeight, $this->_cacheTime, $this->_maxImageLoadTime );
if ( $thumbnailUrl )
{
$imageTagStartPos = JString::strpos( $articleText, $imageCode );
$imageTagLength = JString::strlen( $imageCode );
/* Remove the code for the image on the old location. */
$articleText = JString::substr_replace( $articleText, '', $imageTagStartPos, $imageTagLength );
$this->_cache->Set( 'image_tag_start_pos', $imageTagStartPos );
$this->_cache->Set( 'image_tag_length', $imageTagLength );
$this->_cache->Set( 'image_url', $imageUrl );
$this->_cache->Set( 'thumbnail_url', $thumbnailUrl );
$matchCount = false; /* end the loop */
}
else
{
/* Prepare the next iteration
* - The value of $offset determines where the next search starts.
*/
$offset = $matches[ count( $matches ) - 1 ][1] + 1;
$matchCount = preg_match( self::_imgPattern, $articleText, $matches, PREG_OFFSET_CAPTURE, $offset );
}
}
return $thumbnailUrl;
}
/**
* Searches for a tag which indicates a content gallery plugin is running. If one is found, it will search through the
* content gallery for a suitable image (i.e. an image that satisfies the configuration constraints), and returns HTML
* code containing a thumbnail to that image, readily suitable for display.
* @param string $articleText IN, OUT. the string to search in and to adapt.
* @return String. The HTML code for displaying the styled, resized and linked first image according to the
* configuration settings, the empty string otherwise.
* @note When an image is found, the results will have been cached when this function returns.
*/
private function _PopGalleryImage( &$articleText )
{
$thumbnailUrl = false;
$matchCount = preg_match( self::_galleryPattern, $articleText, $matches, PREG_OFFSET_CAPTURE, 0 );
while ( $matchCount )
{
$galleryCode = $matches[0][0];
$galleryPath = $matches[2][0];
/* No acceptable image has been found yet, try this one. */
if ( $directoryContents = @dir( JPATH_SITE . '/images/' . $galleryPath ) )
{
while ( !/*NOT*/ $thumbnailUrl and $imagePath = $directoryContents->read() )
{
$imagePath = JPATH_SITE . '/images/' . $imagePath;
$thumbnailUrl = ReadLessTextThumb::GetThumbnail( $imagePath, $this->_minimum, $this->_crop,
$this->_thumbWidth, $this->_thumbHeight, $this->_cacheTime, $this->_maxImageLoadTime );
}
$directoryContents->close();
}
if ( $thumbnailUrl )
{
$imageTagStartPos = JString::strpos( $articleText, $galleryCode );
$imageTagLength = JString::strlen( $galleryCode );
/* Remove the code for the image on the old location. */
$articleText = JString::substr_replace( $articleText, '', $imageTagStartPos, $imageTagLength );
$this->_cache->Set( 'image_tag_start_pos', $imageTagStartPos );
$this->_cache->Set( 'image_tag_length', $imageTagLength );
$this->_cache->Set( 'image_url', $imagePath );
$this->_cache->Set( 'thumbnail_url', $thumbnailUrl );
$matchCount = false; /* end the loop */
}
else
{
/* Prepare the next iteration
* - The value of $offset determines where the next search starts.
*/
$offset = $matches[ count( $matches ) - 1 ][1] + 1;
$matchCount = preg_match( self::_imgPattern, $articleText, $matches, PREG_OFFSET_CAPTURE, $offset );
}
}
return $thumbnailUrl;
}
/**
* Composes the attributes for the moved image, inclusive the inline CSS
* attribute @c style.
* This function does not make any modification.
* @param alt: A string. The alternative text to be used for the image. Is also used as image title
* @param $width: A Number. The size of the image in pixels. If 0 or negative, this value is not set.
* @param $height: A Number. The size of the image in pixels. If 0 or negative, this value is not set.
* @return string: Either the empty string when no attributes are needed;
* either a string ready for insertion as an attribute in a HTML tag.
* @note Double quotes are used to quote the value.
*/
private function _GetImageAttributes( $alt, $width = 0, $height = 0 )
{
$attributes = '';
$styleValue = '';
$attributes .= ' alt="' . $alt . '"';
$attributes .= ' title="' . $alt . '"';
if ( $width > 0 )
{
$attributes .= ' width="' . $width . '"';
}
if ( $height > 0 )
{
$attributes .= ' height="' . $height . '"';
}
$class = $this->params->get( 'thumbClass', '' );
if ( $class )
{
$attributes .= ' class="' . $class . '"';
}
$imagePosition = $this->params->get( 'thumbPosition', 'left' );
if ( $imagePosition )
{
$styleValue .= ' float:' . $imagePosition . ';';
}
$padding = $this->params->get( 'thumbPadding', -1 );
if ( $padding >= 0 )
{
$styleValue .= ' padding:' . $padding . 'px;';
}
$margin = $this->params->get( 'thumbMargin', '' );
if ( $margin and ( $margin[0] !== '-' /* a negative number starts with - */ ) )
{
$margin = JString::strtolower( $margin );
$search = array( ' ', ';' );
$replace = array( ',', ',' );
$margin = JString::str_ireplace( $search, $replace, $margin );
$marginList = explode( ',', $margin );
$n = 0;
while ( $n < count( $marginList ) )
{
if ( $marginList[ $n ] or ( $marginList[ $n ] === '0' ) )
{
if ( JString::strpos( '0123456789', $marginList[ $n ][ JString::strlen( $marginList[ $n ] ) - 1 ] ) === FALSE )
{
/* Ok, a unit is already appended */
}
else
{
$marginList[ $n ] .= "px";
}
$marginList[ $n ] = ' ' . $marginList[ $n ];
$n++;
}
else
{
unset( $marginList[ $n ] );
$marginList = array_values( $marginList ); /* re-index */
}
}
$margin = implode( '', $marginList );
$styleValue .= ' margin:' . $margin . ';';
}
$borderWidth = $this->params->get ( 'thumbBorderWidth', - 1 );
if ($borderWidth >= 0) {
$borderColor = $this->params->get ( 'thumbBorderColor', '#cccccc' );
$borderStyle = $this->params->get ( 'thumbBorderStyle', '' );
$styleValue .= ' border: ' . $borderWidth . 'px ' . $borderStyle . ' ' . $borderColor . ';';
}
if ( $styleValue )
{
$attributes .= ' style="' . $styleValue . '"';
}
return $attributes;
}
/**
* Searches the given text for [token]...[/token] or {token}...{/token} constructs that must be removed.
* @param string $text The text too operate on.
* @note Possibly writes isShortened.
* @return The stripped text.
*/
private function _StripTokens( $text )
{
$settings = array (
array ( 'pattern' => self::_tokenPattern,
'opening' => '\[',
'closing' => '\]',
'tokens' => $this->_squareTokensToRemove
),
array ( 'pattern' => self::_tokenPatternWithContents,
'opening' => '\[',
'closing' => '\]',
'tokens' => $this->_squareTokensToRemoveWithContents
),
array ( 'pattern' => self::_tokenPattern,
'opening' => '{',
'closing' => '}',
'tokens' => $this->_curlyTokensToRemove
),
array ( 'pattern' => self::_tokenPatternWithContents,
'opening' => '{',
'closing' => '}',
'tokens' => $this->_curlyTokensToRemoveWithContents
) );
/* Remove first without contents, then remove with contents. */
foreach ( $settings as $setting )
{
foreach ( $setting[ 'tokens' ] as $token )
{
$search = array( self::_tokenOpeningReplacer, self::_tokenClosingReplacer, self::_tokenReplacer );
$replace = array( $setting[ 'opening' ], $setting[ 'closing' ], $token );
$pattern = JString::str_ireplace( $search, $replace, $setting[ 'pattern' ] );
$count = 0;
$text = preg_replace( $pattern, '', $text, -1, $count );
if ( $count )
{
/* We have stripped away a - likely fancy - part of the article. The user must
* get the possibility to access the full article - and thus that fancy part.
*/
$this->_isShortened = true;
}
}
}
return $text;
}
/**
* Cuts off the article text, retaining the full formatting. It uses regular expressions to find tags, and a simple
* push/pop system to retain the opened but still-unclosed tags. When the text has been cut off after a
* admin-configurable unit count, all the opened and still-unclosed tags are then closed.
* @note No prefix nor suffix will be added in this function.
* @post If the shortened string differs from the given @c text, @c _isShortened is set to @c true.
* @param string $text RO. The HTML text to shorten.
* @param integer $cutOffLength The size in number of @c _lengthUnit units of the plain text to retain, or @c false
* to indicate all text must be retained (safe the tags that must be removed according to the configuraion
* settings).
* @return An array of strings: first 'the shortened text', second all 'the closing tags'. The second string may be
* empty - this indicates all text was to be retained, safe for the @c _tagsToRemove and
* @c _tagsToRemoveWithContents.
*/
private function _CutOff( $text, $cutOffLength )
{
/* Treated as a fifo stack. A list of tags that have been opened but not yet closed. */
$openTags = array();
/* Portions of $text is continuously appended until the $cutOffLength is reached.
* Will be returned as 'the shortened text'.
*/
$shortened = array();
/* Also the tag that follows whitespace is not immediately added to $shortened.
* It is first stored in here.
* When a new portion is found, this string is fully appended to $shortened.
* When the loops ends, this string is expanded with all closing tags that still
* need to be added (one for each opened tag that is still not yet closed) are
* appended here at the end of this fucmtion, before returning.
* Will be returned as 'the closing tags'
*/
$closingTags = '';
/* The total length of the plain text strings may not be bigger than the
* maximum article length setting.
*/
if ( $cutOffLength )
{
$nrOfUnitsYetToRetain = $cutOffLength;
}
else
{
$nrOfUnitsYetToRetain = PHP_INT_MAX;
}
/* Use an integer here, not a boolean.
* If a tag is found which has to be removed with its content - i.e. it is
* listed in @c _tagsToRemoveWithContents - we search for that specific
* tagin the next loop. If can be that that same tag is nested, and that
* an opening tag is found again. By using a counter we can be sure when
* the outer closing tag has been found.
*/
$removeAllUntilEndOfTag = 0;
/* The position in bytes from where the search for the next match of the regular expression must start. */
$offset = 0;
/* A tag does not necessarily indicate the end of the 'word', 'sentence', or 'paragraph'.
* When this boolean is false, the (first portion of the) next plaintext indicates the start of a new unit.
* When this boolean is true, the (first portion of the) next plaintext belongs to the same 'word', etc.
*/
$currentUnitIsOngoing = false;
/* This is not a constant: normally we search for any tag, but when we know we have to skip portions
* we can as well directly search for the corresponding end tag. This can be achieved by adapting the
* tag pattern.
*/
$tagPattern = JString::str_ireplace( self::_tagReplacer, self::_anyTag, self::_tagPattern );
$continue = true;
while ( $continue )
{
/* Find HTML tags. Each match is an array of (mostly) interesting data
* about the tag - see @c _tagPattern. Using that, we can find the plain
* text in front of it.
*/
/* By using PREG_OFFSET_CAPTURE, each entry in the array returned will
* be an array itself, containing the matched (sub)string and the starting
* offset.
*/
$matchCount = preg_match( $tagPattern, $text, $matches, PREG_OFFSET_CAPTURE, $offset );
if ( !/*NOT*/ $matchCount or ( count( $matches ) < 4 ) )
{
/* No (more) HTML tags were found. We can not just assume that the last part
* of the string is always a HTML tag, especially not when no wysiwig editor
* has been used to create this article.
* Fetch the remainder of text, and set the needed variables to some dummy value,
* so that the loop can finish correctly.
*/
$matchCount = preg_match ( "/.*/is", $text, $matches, PREG_OFFSET_CAPTURE, $offset );
$plainText = $matches[0][0];
$fullTag = "";
$tag = "";
$isSelfClosingTag = false;
$isClosingTag = false;
//$offset = /* Don't care */
$continue = false;
}
else
{
$plainText = $matches[1][0];
$fullTag = $matches[2][0];
$tag = JString::strtolower( $matches[4][0] );
/* Determine whether the tag we found is an opening tag (e.g. <abc>), a closing tag (e.g. </abc>)
* or a self-closing tag (e.g. <abc />).
* If a closing slash is present, it is captured by the regular expression, either in index 3 (<abc />),
* either in the last-but-one index (<abc />) - and the last-but-one index is always greater than 3.
*/
$isSelfClosingTag = $matches[ count( $matches) - 2 ][0] == '/';
$isClosingTag = $matches[3][0] == '/';
/* Those pesky html4 tags are still lingering around. Be sure to
* correctly determine a tag as self-closing, even when no closing
* slash was present. e.g. <br> vs. <br/>
*/
if ( !/*NOT*/ $isClosingTag )
{
if ( in_array( $tag, $this->_extraSelfClosingTags ) )
{
$isSelfClosingTag = true;
}
}
/* Prepare the next loop: the value of $offset determines where the next search starts. */
$offset = $matches[ count( $matches ) - 1 ][1] + 1;
/* $continue remains true for now. May become false below. */
}
if ( $removeAllUntilEndOfTag )
{
/* We're inside a block of code of which nothing may be retained in the cut-off text.
* Do not add the plaintext and tag strings.
*/
if ( $isClosingTag )
{
$removeAllUntilEndOfTag--;
if ( $removeAllUntilEndOfTag <= 0 )
{
$tagPattern = JString::str_ireplace( self::_tagReplacer, self::_anyTag, self::_tagPattern );
}
}
else
{
$removeAllUntilEndOfTag++;
}
}
else
{
/* Determine the length of the just extracted portion of $text.
* Depending on the outcome, and the current value of $currentUnitIsOngoing
* that may need to be tweaked a bit.
* e.g. we need to count in paragraphs, the match contains no plaintext and a paragraph ending:
* the current ongoing paragraph is then closed and must be counted.
* e.g. we need to count in words, the match equals 'def ghi?<b>':
* the returned value will be two, but we may only count one if '?' equals 'j' (two if it equals ' ').
* Also update $currentUnitIsOngoing to reflect the state after the current portion has been added.
*/
$end = true;
$length = ReadLessTextHelper::DetermineLength( $plainText . $fullTag, $this->_lengthUnit, $end );
if ( $currentUnitIsOngoing and $end )
{
if ( !/*NOT*/ $length )
{
$length = 1;
}
}
if ( $length and !/*NOT*/ $end )
{
$length--;
}
if ( $currentUnitIsOngoing )
{
if ( $end )
{
$currentUnitIsOngoing = false;
}
}
else
{
if ( $length and !/*NOT*/ $end )
{
$currentUnitIsOngoing = true;
}
}
/* Add the new plaintext. */
if ( $length >= $nrOfUnitsYetToRetain )
{
$plainText = ReadLessTextHelper::Substr( $plainText, $nrOfUnitsYetToRetain, $this->_lengthUnit, $this->_retainWholeWords );
$shortened[] = $plainText;
$this->_isShortened = true;
//$nrOfUnitsYetToRetain = /* Don't care */
$continue = false;
}
else
{
$shortened[] = $plainText;
$nrOfUnitsYetToRetain -= $length;
/* $continue remains true. */
}
/* Determine what to append to the just-added plain text.
* Also:
* - update the list of opened-and-not-yet-closed tags and
* - prepare the next loop (if there is another one).
*/
if ( in_array( $tag, $this->_tagsToRemoveWithContents ) )
{
/* We don't know what will be stripped away in the shortened text:
* it could be markup, a title or a table.
* For sure is that the author wants this tag with its contents in the
* full article, and that it may not taken along in the shortened
* version. Even when all the remainder of the article fits in the
* shortened text, it is best to ensure a pre- and/or suffix is
* appended when done shortening.
*/
$this->_isShortened = true;
/* Do not add the tag. */
if ( $isClosingTag or $isSelfClosingTag )
{
/* Nothing more to do. */
}
else
{
$removeAllUntilEndOfTag++;
$tagPattern = JString::str_ireplace( self::_tagReplacer, $tag, self::_tagPattern );
}
}
else if ( in_array( $tag, $this->_tagsToRemove )
or in_array( 'all', $this->_tagsToRemove ) )
{
/* Do not add the tag. */
}
else if ( $isSelfClosingTag )
{
$shortened[] = $fullTag;
}
else if ( $isClosingTag )
{
$shortened[] = $fullTag;
/* For simplicity, just assume at this point the text only contains
* valid HTML, i.e. that all opening tags are properly closed in the
* correct order. That means we do not need to check whether some
* pushed opening tag matches with this closing tag: it just has to
* be.
*/
unset( $openTags[ count( $openTags ) - 1 ] );
$openTags = array_values( $openTags ); /* re-index */
}
else if ( $tag )
{
if ( $continue )
{
/* The tag found is a valid opening tag that must remain in the cut-off text. */
$shortened[] = $fullTag;
$openTags[] = $tag;
}
else
{
/* No need to open a tag just at the point where we will stop. */
}
}
}
} /* while ( $continue ) */
/* Not all parts of $shortened will become part of 'the shortened text'.
* All last portions that are either
* - a tag,
* - an empty string,
* - a whitespace string
* will be added to 'the closing tags'.
* Plus - after the removal of these elements - the then last plaintext string is right trimmed.
* All this to allow to 'glue' the inline suffix right after the last retained plaintext
* character.
*/
$continue = true;
$i = count( $shortened ) - 1;
while ( $continue and ( $i > 0 ) )
{
if ( ( $shortened[ $i ] === '' ) /* an empty string */
or ( substr_compare( $shortened[ $i ], '<', 0, 1 ) == 0 ) /* a tag */
or ( ctype_space( $shortened[ $i ] ) ) ) /* a whitespace string */
{
$closingTags = $shortened[ $i ] . $closingTags;
$shortened[ $i ] = '';
$i--;
}
else
{
$continue = false;
}
}
if ( $i > 0 )
{
$shortened[ $i ] = ReadLessTextHelper::Rtrim( $shortened[ $i ] );
}
$shortened = implode( '', $shortened );
/* All tags that were opened and not yet closed are now closed here
* in the correct order: i.e. in reverse order.
*/
if ( count( $openTags ) > 0 )
{
$openTags = array_reverse( $openTags );
$closingTags .= '</' . implode( '></', $openTags ) . '>';
}
return array ( $shortened, $closingTags );
}
/* *********************************************************************** */
/**
* Various options needed while operating on the text.
* May only be set in _PrepareArticleText or _GetParams.
* Once set, it is to be considered RO.
* @{
*/
private $_cache = null; /* Set correctly in _PrepareArticleText(). */
private $_addPrefix = 'no'; /**< Must be checked after shortening, (if applicable) in combination with _isShortened. */
private $_addInlineSuffix = 'no'; /**< Must be checked after shortening, (if applicable) in combination with _isShortened. */
private $_addSuffix = 'no'; /**< Must be checked after shortening, (if applicable) in combination with _isShortened. */
private $_respectExistingReadmoreLink = true; /* Set correctly in _GetParams() */
private $_applyFormatting = 'no'; /**< Must be checked after checking the article's length, (if applicable) in combination with _isShortened. */
private $_createThumbnail = 'no'; /**< Must be checked after shortening, (if applicable) in combination with _isShortened. */
private $_linkThumbnail = true; /* Set correctly in _GetParams() */
private $_thumbnailTitle = ''; /* Set correctly in _GetParams() */
private $_prefix = ''; /* Set correctly in _GetParams() */
private $_inlineSuffix = ''; /* Set correctly in _GetParams() */
private $_suffix = ''; /* Set correctly in _GetParams() */
private $_articleUrl = '.';
private $_retainWholeWords = false;
private $_crop = array( 'horizontal_position' => 'no', 'vertical_position' => 'no' );
private $_cacheTime = 2419200 /* 4 weeks */;
private $_thumbWidth = 0;
private $_thumbHeight = 0;
private $_minimum = array( 'width' => 0, 'height' => 0, 'ratio' => 0.05 );
private $_extraSelfClosingTags = array();
private $_tagsToRemove = array();
private $_tagsToRemoveWithContents = array();
private $_squareTokensToRemove = array();
private $_curlyTokensToRemove = array();
private $_squareTokensToRemoveWithContents = array();
private $_curlyTokensToRemoveWithContents = array();
private $_htmltext = ''; /* Set correctly in _PrepareArticleText() */
private $_plaintext = ''; /* Set correctly in _PrepareArticleText() */
private $_lengthUnit = 'char'; /* Set correctly in _PrepareArticleText() */
private $_wordCount = 0; /* Set correctly in _PrepareArticleText() */
private $_hash = 0; /* Set correctly in _PrepareArticleText(). */
private $_wrapperTag = array( 'open' => '', 'close' => '' ); /* Set correctly in _PrepareArticleText(). */
/** @} */
/**
* Can be set to true, when an existing manually inserted read more token
* is found; or later on, when the formatting options cause the removal of
* parts of the article; or when the article is automatically shortened and
* some remainder of the article is excluded.
* Once set to true, the variable may not be set to false again.
*/
private $_isShortened = false; /* Reset in _GetParams() */
/** @} /*
/* *********************************************************************** */
/**
* Used to find an image tag in a text.
*
* A match found using this regular expression will return at each index:
* [0] The complete @c img tag, inclusive the brackets and the attributes
* [1] The value of @src attribute, i.e. the URL where the image can be fetched.
* [last] The closing bracket. Used to know the precise end byte
* offset of the matched tag. Especially needed for multi byte strings
* (some of the attributes inside the tag might very well be that),
* since JString::strlen returns the number characters, not the number
* of bytes. The offset given to preg_match needs to be a byte offset.
*
* @note: It is not possible (or: I could not get it to work) to include correct
* positions together with the found matches when using non-ASCII UTF8 strings
* when using this pattern with preg_match.
* The matches returned seem correct though, and strpos can be used to fetch
* the starting UTF8 character index.
* @note This is a simplified version of _tagPattern. Potentially this can
* match a great portion of the text, crossing several html tags, _if_ an img
* tag is given _without_ a src attribute. If this happens, give them what they
* are asking for. Or, in other words: don't worry about that.
*/
const _imgPattern = '/<img.+?src\s*=\s*["\']([^"\']+)["\'][^>]*(>)/muis';
/* 1111111 -1 */
/* 00000000000000000000000000000000000000000000 */
/**
* Used to find an content gallery token in a text.
*
* A match found using this regular expression will return at each index:
* [0] The complete @c content gallery token, inclusive the gallery data and the closing token.
* [1] The gallery data, i.e. the relative URL where the gallery images can be fetched.
* [last] The closing bracket. Used to know the precise end byte offset of the matched tag. Especially needed for
* multi byte strings since JString::strlen returns the number characters, not the number of bytes. The offset given
* to preg_match needs to be a byte offset.
*/
const _galleryPattern = '/{(gallery|vsig|ppgallery|becssg)}\s*([^<]+)\s*{\/\1}/muis';
/* 11111 -1 */
/* 0000000000000000000000000000000000000000000000000000 */
/**
* Used to parse an html text by finding all the HTML tags.
* Thanks to http://kev.coolcavemen.com/2007/03/ultimate-regular-expression-for-html-tag-parsing-with-php/
* It is a bit modified, to catch the tag without attributes and brackets,
* the closing tag character '/', and the last char in the match as well.
* @note Before usage, the string @c _tagReplacer in @c _tagPattern must be replaced with the tag to search for,
* or with @c _anyTag
*
* A match found using this regular expression will return at each index:
* [0] The full match; i.e. the concatenation of everything below
* [1] The plaintext preceding the tag
* [2] The complete tag, inclusive the brackets and the attributes
* [3] Either empty, either '/' when the tag is a closing tag.
* [4] The tag name
* [5] The full attributes - possibly not present
* [6] ...
* [last but one] If the tag is self closing: '/'. Else, and if attributes
* are present: the last attribute. Else: the empty string.
* [last] The closing bracket. Used to know the precise end byte
* offset of the matched tag. Especially needed for multi byte strings
* (some of the attributes inside the tag might very well be that),
* since JString::strlen returns the number characters, not the number
* of bytes. The offset given to preg_match needs to be a byte offset.
*
* @note the @c xxx part is to be replaced
* - replace with @c _anyTag to catch any tag
* - replace with 'abc' to catch tag abc
*
* @{
*/
const _tagPattern = "/(.*?)(<(\/?)(xxx)((\s+(\w|\w[\w-]*\w)(\s*=\s*(?:\".*?\"|'.*?'|[^'\">\s]+))?)+\s*|\s*)(\/?)(>))/muis";
/* 111 333 444 --- one attribute -------------------------------------- -2 -1 */
/* --- all attributes from index 5 until index -3 ------------ */
/* 222222222222222222222222222222222222222222222222222222222222222222222222222222222222222 */
/* 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 */
const _tagReplacer = 'xxx';
const _anyTag = '\w+';
/** @} */
/**
* @li _tokenPattern Used to find third party token indications like
* {slider=MySlider}
* {gallery path/to/images}
* @li _tokenPatternWithContents Used to find third party token indications like
* [pgn]...[/pgn]
* {slide}...{slide}...{slides}
*
* What can not be detected:
* @li nested token codes
* @li self closing token codes
* @li escaped starts of token codes
*
* A match found using this regular expression will return at each index:
* @li _tokenPattern
* [0] The complete token code, inclusive the opening and closing character.
* @li _tokenPatternWithContents
* [0] The complete opening token, the complete closign token, and all text in between.
*
* @note the @c xxx, @c yyy and @c zzz parts are to be replaced
* - replace @c xxx with [ or { or ... to catch the start of a token
* - replace @c yyy with ] or } or ... to catch the end of a token
*
* @{
*/
const _tokenPattern = "/xxx\s*.?\s*zzz.*?yyy/muis";
const _tokenPatternWithContents = "/xxx\s*zzz.*?yyy.*?xxx\s*\/\s*zzzs?\s*yyy/muis";
const _tokenOpeningReplacer = 'xxx';
const _tokenClosingReplacer = 'yyy';
const _tokenReplacer = 'zzz';
/** @} */
// private $profiler = null;
// private $profiles = null;
}
?>