| Current Path : /home/purehotels/public_html/plugins/content/easyfolderlistingpro/ |
| Current File : /home/purehotels/public_html/plugins/content/easyfolderlistingpro/easyfolderlistingpro.php |
<?php
/**
* @version 1.7
* @author Michael A. Gilkes (michael@valorapps.com)
* @copyright Michael Albert Gilkes
* @license GNU/GPLv3
Easy Folder Listing Pro Companion Content Plugin
Copyright (C) 2013-2016 Michael Albert Gilkes (Valor Apps)
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
// no direct access
defined('_JEXEC') or die('Restricted access');
//Register the Loggers
JLog::addLogger(array('logger' => 'messagequeue', 'context' => 'com_easyfolderlistingpro'), JLog::EMERGENCY + JLog::ALERT + JLog::CRITICAL + JLog::ERROR + JLog::WARNING + JLog::NOTICE, array('com_easyfolderlistingpro'));
JLog::addLogger(array('logger' => 'formattedtext', 'text_file' => 'valorapps.errors.php'), JLog::EMERGENCY + JLog::ALERT + JLog::CRITICAL + JLog::ERROR + JLog::WARNING, array('com_easyfolderlistingpro'));
JLog::addLogger(array('logger' => 'formattedtext', 'text_file' => 'valorapps.debug.php'), JLog::DEBUG, array('com_easyfolderlistingpro'));
//import the helpers
require_once(JPATH_ADMINISTRATOR.'/components/com_easyfolderlistingpro/helpers/eflphelper.php');
require_once(JPATH_SITE.'/components/com_easyfolderlistingpro/helpers/ValorUtilities.Class.php');
//imports
jimport('joomla.plugin.plugin'); //needed for MVC implementation
jimport('joomla.filesystem.file');
jimport('joomla.environment.uri');
jimport('joomla.utilities.string');
class plgContentEasyFolderListingPro extends JPlugin
{
//define the plugin string
const PLUGIN_STRING = 'easyfolderlistingpro';
//media files
const CSS_STYLES = 'css/styles.css';
const JQY_MODAL_STYLES = 'css/jqymodal.css';
const SCRIPTS_JQY = 'scripts/eflpJQy.js'; //scripts/eflpJQy.min.js
const SCRIPTS_MOO = 'scripts/eflpMoo.js'; //scripts/eflpMoo.min.js
//modal classes and ids
const MODAL_JQY = 'eflp_modal_jqy';
const MODAL_MOO = 'eflp_modal_moo';
const MODAL_ID = 'eflpModal';
//helper Classes location
const HELPER_CLASSES = 'components/com_easyfolderlistingpro/helpers';
//listing name prefix
const LISTING_PREFIX = 'eflp';
//load language files automatically
protected $autoloadLanguage = true;
//holds the full attribute set for an instance
protected $normalized;
//component configuration
protected $config;
//component icons
protected $icons;
//javascript lirbaries loaded
protected static $jQueryLoaded = false;
protected static $mootoolsLoaded = false;
protected static $cssLoaded = false;
protected static $jqModalAdded = false;
//class based Id offset
protected static $idOffset = 0;
//keep track of the ids of the listings created
protected $listingsIds = array();
//holds the current users access levels
protected $userAccess = NULL;
public function __construct(&$subject, $config)
{
//Note: $config contains the params
parent::__construct($subject, $config);
}
/**
* @param string The context of the content being passed to the plugin.
* @param object The article object. Note $article->text is also available
* @param object The article params
* @param int The 'page' number
*
* @return void
*/
public function onContentPrepare($context, &$article, &$params, $page = 0)
{
//Process shortcodes only if plugin is published
if ($this->params->get('enabled', 1))
{
//Handle situation of being called from admin. Smart Search does this.
$app = JFactory::getApplication();
if($app->isAdmin())
{
//set the working directory to the Joomla root
chdir("..");
}
//Simple performance check to determine whether to process further
if (JString::strpos($article->text, self::PLUGIN_STRING) !== false)
{
$this->handleInstances($article, $params);
}
if($app->isAdmin())
{
//return the working directory to what it was before
chdir("administrator");
}
}
}
/**
* Added in 3.1 to accommodate The companion module
*/
public function onListingPrepare(&$fake_article, $profileId = 1)
{
//set the profile parameter to the specified
//$this->params->set('profile', $profileId);
//Simple performance check to determine whether to process further
if (JString::strpos($fake_article->text, self::PLUGIN_STRING) !== false)
{
$this->handleInstances($fake_article, $this->params);
}
}
protected function handleInstances(&$article, &$params)
{
//Clean the text of No-break spaces and Zero-width spaces
$article->text = $this->normalizeSpaces($article->text);
//find and decode the shortcodes in the article
$shortcodes = $this->decodeShortcodes($article->text, self::PLUGIN_STRING, false);
//number of shortcode instanced in the article
$count = count($shortcodes);
//only processes if there are any instances of the plugin in the text
if ($count)
{
//get the Component Configuration
$this->config = EFLPHelper::retrieveConfig();
//get the Component Icons data
$this->icons = EFLPHelper::retrieveIcons();
//Note: since we know that we found instances, setup the environment
$document = JFactory::getDocument();
//reset the page charset via php header to UTF-8
if ($this->config->get('charsetutf8'))
{
$document->setCharset('utf-8');
}
//initialize variable for javascript
$javascript = array();
//Note: we now have potentially different defaults for each instance
for ($i=0; $i < $count; $i++)
{
//we need to get the attributes first
$attributes = $this->decodeAttributes($shortcodes[$i][1]);
//retrieve the normalized attributes
$this->normalized = $this->normalizeAttributes($attributes);
//define a unique id for the listing
$uniqueId = self::LISTING_PREFIX.ucfirst($this->normalized['method']).self::$idOffset.$i;
//add style sheet for unordered list or tables with standard style
$this->loadCSS($this->normalized['method'], $this->normalized['tablestyle']);
//add appropriate javascript framework for this listing.
$this->loadJSFramework($this->normalized['javascript']);
//add the jQuery Modal HTML at the start, if needed
$listingHTML = $this->jQueryModalHTML();
//get the rendered listing HTML
$listingHTML.= $this->renderListingHTML($uniqueId);
//render the custom javascript for this listing
$javascript[] = $this->renderJavascript($uniqueId, $this->normalized['javascript']);
//increase the offset
self::$idOffset++;
//replace the instances of the plugin text on the page with the formatted html
/* Not sure whether to use "str_replace" or "JString::str_ireplace".
However, "JString::str_ireplace" did cause an issue with too many
listings on the page, and only some showing. So, I'll use str_replace
for now. */
$article->text = str_replace($shortcodes[$i][0], $listingHTML, $article->text);
}
//compile all the javascript
$js = $this->compileJavascripts($javascript);
$document->addScriptDeclaration($js);
}
}
protected function normalizeSpaces($text)
{
//Replace the following unicode characters with a space character:
//1. U+00A0 c2 a0 NO-BREAK SPACE
//2. U+200B e2 80 8b ZERO WIDTH SPACE
return preg_replace("/[\x{00a0}\x{200b}]+/u", " ", $text);
}
/**
* Finds and decodes shortcodes with or without eng tag. Also gets attributes
* and inner text.
*
* @return array all matches from PREG_SET_ORDER flag
*/
protected function decodeShortcodes($text, $tag, $hasEndTag = false)
{
//clean the tag
$tag = (string) preg_replace('/[^A-Z_]/i', '', $tag);
//decode the shortcode text
$pattern = '/\{'.$tag.'\b(.*?)\}';
if ($hasEndTag)
{
$pattern.= '(?:(.*?)\{\/'.$tag.'\})';
}
$pattern.= '/s';
//find the patterns
preg_match_all($pattern, $text, $matches, PREG_SET_ORDER);
return $matches;
}
/**
* Decodes all the attributes from a text, and returns them as key/value pairs. This method
* does not look for value-only attributes.
*
* @return array all matches from PREG_SET_ORDER flag
*/
protected function decodeAttributes($text, $casei = true)
{
$attributes = array();
$pattern = '/(\w+)\s*=\s*"([^"]*)"(?:\s|$)|(\w+)\s*=\s*\'([^\']*)\'(?:\s|$)|(\w+)\s*=\s*([^\s\'"]+)(?:\s|$)/';
if ( preg_match_all($pattern, $text, $matches, PREG_SET_ORDER) )
{
foreach ($matches as $match)
{
if (!empty($match[1]))
{
$attributes[$this->attrkey($match[1], $casei)] = stripcslashes($match[2]);
}
elseif (!empty($match[3]))
{
$attributes[$this->attrkey($match[3], $casei)] = stripcslashes($match[4]);
}
elseif (!empty($match[5]))
{
$attributes[$this->attrkey($match[5], $casei)] = stripcslashes($match[6]);
}
}
}
else
{
$attributes[] = ltrim($text);
}
return $attributes;
}
/**
* Simple convenience method to make a given string case insensitive or not
*/
private function attrkey($key, $case_insensative = true)
{
if ($case_insensative)
{
$key = strtolower($key);
}
return $key;
}
/**
* Applies the user-defined shortcode attribute overrides to the profile attributes, and
* provides the final list of attributes for a listing.
*/
protected function normalizeAttributes($attributes)
{
//initialize the defaults to an empty array
$normalized = array();
//normalize based on profile override
if (array_key_exists('profile', $attributes))
{
//check to see if the specified profile name or id is valid
$result = $this->retrieveProfile($attributes['profile']);
if (!empty($result))
{
//profile was valid. properties were retrieved. merge with overrides.
$normalized = array_merge($result[0], $attributes);
}
else
{
//profile was invalid
JLog::add(JText::_('PLG_CONTENT_EFLP_PROFILE_ID_INVALID'), JLog::WARNING, 'com_easyfolderlistingpro');
}
}
//normalize based on profile in plugin manager
if (empty($normalized))
{
//get the profile from the plugin manager selection instead. Note: 1 should be the default
$result = $this->retrieveProfile($this->params->get('profile', 1));
//only the first row in the result is used. index = 0
$normalized = array_merge($result[0], $attributes);
}
//make sure the profile is set to id, and not name
$normalized['profile'] = $normalized['id'];
//first, remove whitespace from the start and end of the folder
$this->normalized['folder'] = trim($this->normalized['folder']);
//trim forward slashes and backslashes from only the end of the folder
$this->normalized['folder'] = rtrim($this->normalized['folder'], "/\\");
//reformat the exclusion strings as arrays of strings
$normalized['exfiles'] = array_filter(array_map('trim', explode(';', $normalized['exfiles'])), 'strlen');
$normalized['exfolders'] = array_filter(array_map('trim', explode(';', $normalized['exfolders'])), 'strlen');
return $normalized;
}
/**
* Get the profile attributes based on the profile id or name.
*/
private function retrieveProfile($key)
{
$db = JFactory::getDbo();
$query = $db->getQuery(true);
$query->select('*');
$query->from('#__eflp_profiles');
if (is_numeric($key))
{
//it is a number so cast to an int
$key = (int)$key;
$query->where('id='.$key);
}
else //not a number
{
//it is a string so cast to a string
$key = (string)$key;
$query->where('title='.$db->quote($key));
}
$db->setQuery($query);
$profile = $db->loadAssocList();
// Check for a database error.
if ($db->getErrorNum())
{
JLog::add($db->getErrorMsg(), JLog::ERROR, 'com_easyfolderlistingpro');
//$e = new JException($db->getErrorMsg());
}
return $profile;
}
/**
* Loads CSS files only once per page. Only loads if unordered list or standard table.
*/
protected function loadCSS($method, $tablestyle)
{
//if (self::$cssLoaded == false && ($method == 'list' || $tablestyle == 'standard'))
if (self::$cssLoaded == false)
{
//add stylesheet
$document = JFactory::getDocument();
$document->addStyleSheet($this->mediapath(true).self::CSS_STYLES);
//set flag
self::$cssLoaded = true;
}
}
protected function mediapath($media = false, $pathonly = true, $root = false)
{
$function = "base";
if ($root) { $function = "root"; };
$path = JURI::$function($pathonly);
if ($pathonly == true)
{
$path.= '/';
}
if ($media == true)
{
$path.= 'media/com_easyfolderlistingpro/';
}
$path = str_replace('\\', '/', $path);
return $path;
}
/**
* Loads the appropriate javascript framework. No framework is loaded if it is Joomla 2.5
* and the user specifies jQuery. In that case it is assumed that the jQuery is loaded by
* template.
*
* Note: the javascript frameworks are only loaded once per request
*/
protected function loadJSFramework($framework, $modalId = self::MODAL_ID)
{
$document = JFactory::getDocument();
if ($framework == 'mootools')
{
if (!self::$mootoolsLoaded)
{
//load Mootols Core + More
JHtml::_('behavior.framework', true);
//use Mootools modal
JHtml::_('behavior.modal', 'a.'.self::MODAL_MOO);
//use custom jquery eflp code
$document->addScript($this->mediapath(true).self::SCRIPTS_MOO);
//set flag
self::$mootoolsLoaded = true;
}
}
else //default = jquery
{
if (!self::$jQueryLoaded)
{
//load Joomla's built-in jQuery
JHtml::_('jquery.framework');
//unobstrusive hack for setTimeout on IE9 and lower
$this->javascriptHackForSetTimeout();
if ($this->normalized['modaltype'] == 'auto')
{
//add built-in jQuery library for Joomla 3+
//Note: jQuery is loaded when bootstrap is loaded
JHtml::_('bootstrap.framework');
//load the bootstrap CSS to support Modal display
$this->jQueryModalCSS();
//use customized jQuery Bootstrap Modal
$this->jQueryModalScript(self::MODAL_JQY, $modalId);
}
//use custom jquery eflp code
$document->addScript($this->mediapath(true).self::SCRIPTS_JQY);
//set flag
self::$jQueryLoaded = true;
}
//mix mootools modal with jquery
if ($this->normalized['modaltype'] == 'mootools')
{
//load Mootols Core + More
JHtml::_('behavior.framework', true);
//use Mootools modal
JHtml::_('behavior.modal', 'a.'.self::MODAL_MOO);
}
}
}
/**
*
*/
protected function jQueryModalCSS($native = false)
{
if ($native)
{
//load all the bootstrap CSS
JHtml::_('bootstrap.loadCss');
}
else
{
//add stylesheet
$document = JFactory::getDocument();
$document->addStyleSheet($this->mediapath(true).self::JQY_MODAL_STYLES);
}
}
/**
* I needed to add this to get setTimeout to work properly with jQuery. Mootools has
* a delay() function that works so well, but jQuery's delay() function is not the same.
*
* See: http://api.jquery.com/delay/
* See also: https://developer.mozilla.org/en-US/docs/Web/API/window.setTimeout#IE_Only_Fix
*/
protected function javascriptHackForSetTimeout()
{
$js = array();
$js[] = '/*@cc_on';
$js[] = ' // conditional IE < 9 only fix';
$js[] = ' @if (@_jscript_version <= 6)';
$js[] = ' (function(f){';
$js[] = ' window.setTimeout =f(window.setTimeout);';
$js[] = ' window.setInterval =f(window.setInterval);';
$js[] = ' })(function(f){return function(c,t){var a=[].slice.call(arguments,2);return f(function(){c.apply(this,a)},t)}});';
$js[] = ' @end';
$js[] = '@*/';
$document = JFactory::getDocument();
$document->addScriptDeclaration(implode("\n", $js));
}
/**
* Link Format Example:
* <a class="eflpro_preview" data-modal="myModal" data-xsize="200" data-ysize="100" href="http://demos.michaelgilkes.com/joomla/images/joomla_logo_black.jpg">Eureka! She's Alive!!</a>
*/
protected function jQueryModalScript($class, $modalId = self::MODAL_ID)
{
$js = EFLPHelper::jQyModalScript($class, $modalId);
$javascript = "jQuery(document).ready(function() {\n";
$javascript.= $js."\n";
$javascript.= "});\n";
$document = JFactory::getDocument();
$document->addScriptDeclaration($javascript);
}
/**
* Renders the required HTML for the Bootstrap modal. This method must be called after
* jQuery frame code is loaded.
*/
protected function jQueryModalHTML($modalId = self::MODAL_ID)
{
$modal = '';
if (!self::$jqModalAdded && self::$jQueryLoaded && $this->normalized['modaltype'] == 'auto')
{
$modal = EFLPHelper::jQyModalHTML($modalId, 'PLG_CONTENT_EFLP_PREVIEW_WORD');
self::$jqModalAdded = true;
}
return $modal;
}
protected function renderListingHTML($id)
{
//check to see if the profile is enabled
if (!$this->normalized['published'])
{
if ($this->normalized['unpublishedlisting'])
{
return $this->customError(JText::_('PLG_CONTENT_EFLP_PROFILE_UNPUBLISHED'));
}
else
{
return '';
}
}
//check to see if the user has right access level for profile
if (!ValorUtilities::isUserAllowed($this->normalized['access']))
{
if ($this->normalized['useraccessnotallowed'])
{
return $this->customError(JText::_('PLG_CONTENT_EFLP_USERACCESS_NOT_ALLOWED'));
}
else
{
return '';
}
}
//declare a variable to hold listing data object
$data = null;
//check to see if the normalized path is empty
if (empty($this->normalized['folder']))
{
if ($this->normalized['emptylistingpath'])
{
//can't make a listing from an empty folder location.
return $this->customError(JText::_('PLG_CONTENT_EFLP_LISTING_PATH_EMPTY'));
}
else
{
return '';
}
}
else
{
//now, we can get the files data
require_once(JPath::clean(JPATH_SITE.'/'.self::HELPER_CLASSES."/ListingData.Class.php"));
$data = new ListingData($this->normalized);
}
//determine file depth and sorting order based on type of listing
$ignore_subfolders = false;
$preserve_folders = true;
//paginated tables doesn't preserve folder order for files
if ($this->normalized['method'] == 'page')
{
$preserve_folders = false;
}
/** Commenting this out deliberately!
elseif ($this->normalized['method'] == 'explorer')
{
$ignore_subfolders = true;
$preserve_folders = false;
}*/
//get the processed files data
$files = $data->getFilesData($ignore_subfolders, $preserve_folders);
//check to see if there are errors from processing the files data
if (count($data->getErrors()))
{
EFLPHelper::logErrors($data->getErrors());
return '';
}
//if the files is empty, get the errors and display them
if (empty($files))
{
if ($this->normalized['nofilesfound'])
{
return $this->customError(JText::sprintf('PLG_CONTENT_EFLP_LISTING_NO_FILES', $this->normalized['folder']));
}
else
{
return '';
}
}
//seems like we need to get the folders too
$folders = $data->getFoldersData($ignore_subfolders);
//check to see if there are errors from processing the folders data
if (count($data->getErrors()))
{
EFLPHelper::logErrors($data->getErrors());
return '';
}
//initialize listing to null
$listing = null;
//render the listing based on its type
if ($this->normalized['method'] == 'table')
{
require_once(JPath::clean(JPATH_SITE.'/'.self::HELPER_CLASSES."/EFLPTable.Class.php"));
$listing = new EFLPTable($id, $this->normalized, $this->config, $this->icons, $files, $folders);
}
elseif ($this->normalized['method'] == 'page')
{
require_once(JPath::clean(JPATH_SITE.'/'.self::HELPER_CLASSES."/EFLPPageTable.Class.php"));
$listing = new EFLPPageTable($id, $this->normalized, $this->config, $this->icons, $files, $folders);
}
elseif ($this->normalized['method'] == 'explorer')
{
require_once(JPath::clean(JPATH_SITE.'/'.self::HELPER_CLASSES."/EFLPExplorerList.Class.php"));
$listing = new EFLPExplorerList($id, $this->normalized, $this->config, $this->icons, $files, $folders);
}
else //method == 'list'
{
require_once(JPath::clean(JPATH_SITE.'/'.self::HELPER_CLASSES."/EFLPList.Class.php"));
$listing = new EFLPList($id, $this->normalized, $this->config, $this->icons, $files, $folders);
}
if (empty($listing))
{
JLog::add(JText::_('PLG_CONTENT_EFLP_INSTANTIATING_LISTING_OBJECT_ERROR'), JLog::ERROR, 'com_easyfolderlistingpro');
return '';
}
return $listing->renderHTML();
}
/**
* Used to display an inline error.
*/
protected function customError($message)
{
return '<img src="media/system/images/notice-note.png"><span style="font-weight:bold;">'.$message.'</span>';
}
/**
* Renders the apropriate javascript object code
*
* @return array 2-element array: string of javascript code and framework type
*/
protected function renderJavascript($id, $framework)
{
//initialize the array
$js_array = array();
//determine the framework. Force it to be jquery.
if ($framework != 'jquery' && $framework != 'mootools')
{
$framework = 'jquery';
}
//assign the framework type
$js_array['framework'] = $framework;
//initialize the code to empty string
$js_array['code'] = '';
//Note: Paginated and Explorer listings don't need any javascript
if ($this->normalized['method'] == 'table' || $this->normalized['method'] == 'list')
{
//specify the collapsed value in a way javascript understands
$collapsed = (($this->normalized['collapse']==true) ? 'true': 'false');
//initialize the js to empty string
$js = '';
if ($framework == 'mootools')
{
$class = 'EFLPMooList';
if ($this->normalized['method'] == 'table')
{
$class = 'EFLPMooTable';
}
$js = 'var eflproListing'.$id." = new ".$class."('".$id."', { ";
$js.= "speed:'".$this->normalized['mduration']."', ";
$js.= "easing:'".$this->normalized['measing']."', ";
$js.= "collapsed:".$collapsed." ";
$js.= "});\n";
}
else //else 'jquery'
{
$class = 'EFLPJQyList';
if ($this->normalized['method'] == 'table')
{
$class = 'EFLPJQyTable';
}
$js = 'var eflproListing'.$id." = new ".$class."('".$id."', { ";
$js.= "transition:'".$this->normalized['jtransition']."', ";
$js.= "speed:'".$this->normalized['jduration']."', ";
$js.= "easing:'".$this->normalized['jeasing']."', ";
$js.= "collapsed:".$collapsed." ";
$js.= "});\n";
$js.= 'eflproListing'.$id.'.setupListing();'."\n";
}
//set the javascript code
$js_array['code'] = $js;
}
return $js_array;
}
/**
* @param array multi-dimensional array of javascript code and framework
*
*/
protected function compileJavascripts($js)
{
$jqy = '';
$moo = '';
$javascript = '/* -- Start EFLP Javascript -- */'."\n";
foreach ($js as $i => $script)
{
if ($script['framework'] == 'jquery')
{
$jqy.= $script['code'];
}
else //mootools
{
$moo.= $script['code'];
}
}
if (!empty($jqy))
{
$javascript.= "jQuery(document).ready(function() {\n";
$javascript.= $jqy;
$javascript.= "});\n";
}
if (!empty($moo))
{
$javascript.= "window.addEvent('domready', function() {\n";
$javascript.= $moo;
$javascript.= "});\n";
}
$javascript.= '/* -- End EFLP Javascript -- */'."\n";
return $javascript;
}
}