| Current Path : /home/atice/www/components/com_fabrik/fabrik/fabrik/Helpers/ |
| Current File : /home/atice/www/components/com_fabrik/fabrik/fabrik/Helpers/Worker.php |
<?php
/**
* Generic tools that all models use
*
* @package Joomla
* @subpackage Fabrik
* @copyright Copyright (C) 2005-2020 Media A-Team, Inc. - All rights reserved.
* @license GNU/GPL http://www.gnu.org/copyleft/gpl.html
*/
namespace Fabrik\Helpers;
// No direct access
defined('_JEXEC') or die('Restricted access');
use Fabrik\Helpers\FCipher;
use DateTime;
use Exception;
use FabTable;
use JAccess;
use JCache;
use JComponentHelper;
use JCrypt;
use JCryptCipherSimple;
use JCryptKey;
use JDatabaseDriver;
use JFactory;
use JFile;
use JFilterInput;
use JForm;
use JHtml;
use JLanguageHelper;
use JLanguageMultilang;
use JLog;
use JMail;
use JMailHelper;
use JModelLegacy;
use Joomla\CMS\Application\CMSApplication;
use JPath;
use JSession;
use JTable;
use JUri;
use JVersion;
use RuntimeException;
/**
* Generic tools that all models use
* This code used to be in models/parent.php
*
* @package Joomla
* @subpackage Fabrik.helpers
* @since 3.0
*/
class Worker
{
/**
* Fabrik database objects
*
* @var JDatabaseDriver[]
*/
public static $database = null;
/**
* Fabrik db connections
*
* @var array
*/
public static $connection = null;
/**
* Plugin manager
*
* @var object
*/
public static $pluginManager = null;
/**
* Strtotime final date format
*
* @var string
*/
static protected $finalFormat = null;
/**
* Add slashes in parse message
*
* @var bool
*/
protected $parseAddSlashes = false;
/**
* Search data to replace placeholders
*
* @var array
*/
protected $_searchData = array();
/**
* Get array of valid view types
*
* @return array
*/
public static function getViewTypes()
{
return array(
'article',
'cron',
'csv',
'details',
'element',
'form',
'list',
'package',
'visualization'
);
}
/**
* Returns true if $view is a valid view type
*
* @param string $view View type
*
* @return bool
*/
public static function isViewType($view)
{
$view = strtolower(trim($view));
$viewTypes = self::getViewTypes();
return in_array($view, $viewTypes);
}
/**
* Returns true if $file has an image extension type
*
* @param string $file Filename
*
* @return bool
*/
public static function isImageExtension($file)
{
$path_parts = pathinfo($file);
if (array_key_exists('extension', $path_parts))
{
$image_extensions_eregi = 'bmp|gif|jpg|jpeg|png';
return preg_match('/' . $image_extensions_eregi . '/i', $path_parts['extension']) > 0;
}
return false;
}
/**
* Get audio mime type array, keyed by file extension
*
* @return array
*/
public static function getAudioMimeTypes()
{
return array(
'mp3' => 'audio/x-mpeg',
'm4a' => 'audio/x-m4a'
);
}
/**
* Get audio mime type array, keyed by file extension
*
* @return array
*/
public static function getImageMimeTypes()
{
return array(
'png' => 'image/png',
'gif' => 'image/gif',
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'bmp' => 'image/bmp',
'webp' => 'image/webp'
);
}
/**
* Get document mime type array, keyed by file extension
*
* @return array
*/
public static function getDocMimeTypes()
{
return array(
'pdf' => 'application/pdf',
'epub' => 'document/x-epub'
);
}
/**
* Get video mime type array, keyed by file extension
*
* @return array
*/
public static function getVideoMimeTypes()
{
return array(
'mp4' => 'video/mp4',
'm4v' => 'video/x-m4v',
'mov' => 'video/quicktime'
);
}
/**
* Get Audio Mime type
*
* @param string $file Filename
*
* @return bool|string
*/
public static function getAudioMimeType($file)
{
$path_parts = pathinfo($file);
$types = self::getAudioMimeTypes();
return ArrayHelper::getValue(
$types,
ArrayHelper::getValue($path_parts, 'extension', ''),
false
);
}
/**
* Get Audio Mime type
*
* @param string $file Filename
*
* @return bool|string
*/
public static function getImageMimeType($file)
{
$path_parts = pathinfo($file);
$types = self::getImageMimeTypes();
return ArrayHelper::getValue(
$types,
ArrayHelper::getValue($path_parts, 'extension', ''),
false
);
}
/**
* Get Video Mime type
*
* @param string $file Filename
*
* @return bool|string
*/
public static function getVideoMimeType($file)
{
$path_parts = pathinfo($file);
$types = self::getVideoMimeTypes();
return ArrayHelper::getValue(
$types,
ArrayHelper::getValue($path_parts, 'extension', ''),
false
);
}
/**
* Get Doc Mime type
*
* @param string $file Filename
*
* @return bool|string
*/
public static function getDocMimeType($file)
{
$path_parts = pathinfo($file);
$types = self::getDocMimeTypes();
return ArrayHelper::getValue(
$types,
ArrayHelper::getValue($path_parts, 'extension', ''),
false
);
}
/**
* Get Podcast Mime type
*
* @param string $file Filename
*
* @return bool|string
*/
public static function getPodcastMimeType($file)
{
$mime_type = false;
if ($mime_type = self::getVideoMimeType($file))
{
return $mime_type;
}
elseif ($mime_type = self::getAudioMimeType($file))
{
return $mime_type;
}
elseif ($mime_type = self::getDocMimeType($file))
{
return $mime_type;
}
elseif ($mime_type = self::getImageMimeType($file))
{
return $mime_type;
}
return $mime_type;
}
/**
* Format a string to datetime
*
* http://fr.php.net/strftime
* (use as strftime)
*
* @param string $date String date to format
* @param string $format Date format strftime format
*
* @return array|void date info
*/
public static function strToDateTime($date, $format)
{
$weekdays = array('Sun' => '0', 'Mon' => '1', 'Tue' => '2', 'Wed' => '3', 'Thu' => '4', 'Fri' => '5', 'Sat' => '6');
if (!($date = self::str2Time($date, $format)))
{
return;
}
$shortMonths = array(Text::_('Jan'), Text::_('Feb'), Text::_('Mar'), Text::_('Apr'), Text::_('May'), Text::_('Jun'), Text::_('Jul'),
Text::_('Aug'), Text::_('Sept'), Text::_('Oct'), Text::_('Nov'), Text::_('Dec'));
/*$$ rob set day default to 1, so that if you have a date format string of %m-%Y the day is set to the first day of the month
* and not the last day of the previous month (which is what a 0 here would represent)
*/
$dateTime = array('sec' => 0, 'min' => 0, 'hour' => 0, 'day' => 1, 'mon' => 0, 'year' => 0, 'timestamp' => 0);
foreach ($date as $key => $val)
{
switch ($key)
{
case 'd':
case 'e':
case 'j':
$dateTime['day'] = intval($val);
break;
case 'D':
$dateTime['day'] = intval($weekdays[$val]);
break;
case 'm':
case 'n':
$dateTime['mon'] = intval($val);
break;
case 'b':
$dateTime['mon'] = $shortMonths[$val] + 1;
break;
case 'Y':
$dateTime['year'] = intval($val);
break;
case 'y':
$dateTime['year'] = intval($val) + 2000;
break;
case 'G':
case 'g':
case 'H':
case 'h':
$dateTime['hour'] = intval($val);
break;
case 'M':
$dateTime['min'] = intval($val);
break;
case 'i':
$dateTime['min'] = intval($val);
break;
case 's':
case 'S':
$dateTime['sec'] = intval($val);
break;
}
}
$dateTime['timestamp'] = mktime($dateTime['hour'], $dateTime['min'], $dateTime['sec'], $dateTime['mon'], $dateTime['day'], $dateTime['year']);
return $dateTime;
}
/**
* Check for, and convert, any 'special' formats for strtotime, like 'yesterday', etc.
*
* @param string $date Date to check
* @param bool $gmt Set date to universal time?
*
* @return string date
*/
public static function specialStrToMySQL($date, $gmt = true)
{
/**
* $$$ hugh - if date is empty, just return today's date
*/
if (empty($date))
{
$d = JFactory::getDate();
$date = $d->toSql(!$gmt);
return $date;
}
/**
* lets check if we have some special text as per :
* http://php.net/strtotime - this means we can use "+2 week" as a url filter
* do this before we urldecode the date otherwise the + is replaced with ' ';
*/
$matches = array();
$matches2 = array();
$matches3 = array();
// E.g. now
preg_match("/(now|ago|midnight|yesterday|today)/i", $date, $matches);
// E.g. +2 Week
preg_match("/[+|-][0-9]* (week\b|year\b|day\b|month\b)/i", $date, $matches2);
// E.g. next Wednesday
preg_match("/[next|last]* (monday\b|tuesday\b|wednesday\b|thursday\b|friday\b|saturday\b|sunday\b)/i", $date, $matches3);
$matches = array_merge($matches, $matches2, $matches3);
if (!empty($matches))
{
$d = JFactory::getDate($date);
$date = $d->toSql(!$gmt);
}
return $date;
}
/**
* String to time
*
* @param string $date Date representation
* @param string $format Date format
*
* @return array date bits keyed on date representations e.g. m/d/Y
*/
public static function str2Time($date, $format)
{
/**
* lets check if we have some special text as per :
* http://php.net/strtotime - this means we can use "+2 week" as a url filter
* do this before we urldecode the date otherwise the + is replaced with ' ';
*/
$matches = array();
$matches2 = array();
$matches3 = array();
// E.g. now
preg_match("/[now|ago|midnight|yesterday|today]/i", $date, $matches);
// E.g. +2 Week
preg_match("/[+|-][0-9]* (week\b|year\b|day\b|month\b)/i", $date, $matches2);
// E.g. next Wednesday
preg_match("/[next|last]* (monday\b|tuesday\b|wednesday\b|thursday\b|friday\b|saturday\b|sunday\b)/i", $date, $matches3);
$matches = array_merge($matches, $matches2, $matches3);
if (!empty($matches))
{
$d = JFactory::getDate($date);
$date = $d->format($format);
}
/* $$$ - hugh : urldecode (useful when ajax calls, may need better fix)
* as per http://fabrikar.com/forums/showthread.php?p=43314#post43314
*/
$date = urldecode($date);
// Strip any textual date representations from the string
$days = array('%A', '%a');
foreach ($days as $day)
{
if (strstr($format, $day))
{
$format = str_replace($day, '', $format);
$date = self::stripDay($date, $day == '%a' ? true : false);
}
}
$months = array('%B', '%b', '%h');
foreach ($months as $month)
{
if (strstr($format, $month))
{
$format = str_replace($month, '%m', $format);
$date = self::monthToInt($date, $month == '%B' ? false : true);
}
}
// @TODO: some of these aren't right for strftime
self::$finalFormat = $format;
$search = array('%d', '%e', '%D', '%j', '%m', '%b', '%Y', '%y', '%g', '%H', '%h', '%i', '%s', '%S', '%M');
$replace = array('(\d{2})', '(\d{1,2})', '(\w{3})', '(\d{1,2})', '(\d{2})', '(\w{3})', '(\d{4})', '(\d{2})', '(\d{1,2})', '(\d{2})',
'(\d{2})', '(\d{2})', '(\d{2})', '(\d{2})', '(\d{2})');
$pattern = str_replace($search, $replace, $format);
if (!preg_match("#$pattern#", $date, $matches))
{
// Lets allow for partial date formats - e.g. just the date and ignore the time
$format = explode('%', $format);
if (empty($format))
{
// No format left to test so return false
return false;
}
array_pop($format);
$format = trim(implode('%', $format));
self::$finalFormat = $format;
return self::str2Time($date, $format);
}
$dp = $matches;
if (!preg_match_all('#%(\w)#', $format, $matches))
{
return false;
}
$id = $matches['1'];
if (count($dp) != count($id) + 1)
{
return false;
}
$ret = array();
for ($i = 0, $j = count($id); $i < $j; $i++)
{
$ret[$id[$i]] = $dp[$i + 1];
}
return $ret;
}
/**
* Removed day of week name from string
*
* @param string $date The string date
* @param bool $abrv Abbreviated day?
*
* @return string date
*/
public static function stripDay($date, $abrv = false)
{
if ($abrv)
{
$date = str_replace(Text::_('SUN'), '', $date);
$date = str_replace(Text::_('MON'), '', $date);
$date = str_replace(Text::_('TUE'), '', $date);
$date = str_replace(Text::_('WED'), '', $date);
$date = str_replace(Text::_('THU'), '', $date);
$date = str_replace(Text::_('FRI'), '', $date);
$date = str_replace(Text::_('SAT'), '', $date);
}
else
{
$date = str_replace(Text::_('SUNDAY'), '', $date);
$date = str_replace(Text::_('MONDAY'), '', $date);
$date = str_replace(Text::_('TUESDAY'), '', $date);
$date = str_replace(Text::_('WEDNESDAY'), '', $date);
$date = str_replace(Text::_('THURSDAY'), '', $date);
$date = str_replace(Text::_('FRIDAY'), '', $date);
$date = str_replace(Text::_('SATURDAY'), '', $date);
}
return $date;
}
/**
* Convert a month (could be in any language) into the month number (1 = jan)
*
* @param string $date Data to convert
* @param bool $abrv Is the month is a short or full name version
*
* @return string
*/
public static function monthToInt($date, $abrv = false)
{
if ($abrv)
{
$date = str_replace(Text::_('JANUARY_SHORT'), '01', $date);
$date = str_replace(Text::_('FEBRUARY_SHORT'), '02', $date);
$date = str_replace(Text::_('MARCH_SHORT'), '03', $date);
$date = str_replace(Text::_('APRIL_SHORT'), '04', $date);
$date = str_replace(Text::_('MAY_SHORT'), '05', $date);
$date = str_replace(Text::_('JUNE_SHORT'), '06', $date);
$date = str_replace(Text::_('JULY_SHORT'), '07', $date);
$date = str_replace(Text::_('AUGUST_SHORT'), '08', $date);
$date = str_replace(Text::_('SEPTEMBER_SHORT'), '09', $date);
$date = str_replace(Text::_('OCTOBER_SHORT'), 10, $date);
$date = str_replace(Text::_('NOVEMBER_SHORT'), 11, $date);
$date = str_replace(Text::_('DECEMBER_SHORT'), 12, $date);
}
else
{
$date = str_replace(Text::_('JANUARY'), '01', $date);
$date = str_replace(Text::_('FEBRUARY'), '02', $date);
$date = str_replace(Text::_('MARCH'), '03', $date);
$date = str_replace(Text::_('APRIL'), '04', $date);
$date = str_replace(Text::_('MAY'), '05', $date);
$date = str_replace(Text::_('JUNE'), '06', $date);
$date = str_replace(Text::_('JULY'), '07', $date);
$date = str_replace(Text::_('AUGUST'), '08', $date);
$date = str_replace(Text::_('SEPTEMBER'), '09', $date);
$date = str_replace(Text::_('OCTOBER'), 10, $date);
$date = str_replace(Text::_('NOVEMBER'), 11, $date);
$date = str_replace(Text::_('DECEMBER'), 12, $date);
}
return $date;
}
/**
* Check a string is not reserved by Fabrik
*
* @param string $str To check
* @param bool $strict Include things like rowid, listid in the reserved words, defaults to true
*
* @return bool
*/
public static function isReserved($str, $strict = true)
{
$reservedWords = array("task", "view", "layout", "option", "formid", "submit", "ul_max_file_size"
, "ul_file_types", "ul_directory", 'adddropdownvalue', 'adddropdownlabel', 'ul_end_dir');
/*
* $$$ hugh - a little arbitrary, but need to be able to exclude these so people can create lists from things like
* log files, which include field names like rowid and itemid. So when saving an element, we now set strict mode
* to false if it's not a new element.
*/
$strictWords = array("listid", 'rowid', 'itemid');
if ($strict)
{
$reservedWords = array_merge($reservedWords, $strictWords);
}
if (in_array(StringHelper::strtolower($str), $reservedWords))
{
return true;
}
return false;
}
/**
* Check a string is valid to use as an element name
*
* @param string $str To check
* @param bool $strict Include things like rowid, listid in the reserved words, defaults to true
*
* @return bool
*/
public static function validElementName($str, $strict = true)
{
// check if it's a Fabrik reserved word
if (self::isReserved($str, $strict))
{
return false;
}
// check valid MySQL - start with letter or _, then only alphanumeric or underscore
if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $str))
{
return false;
}
// check for various other gotchas, like ending in _raw, starting with more than one _, etc.
if (preg_match('/^submit|^__|_raw$/', $str))
{
return false;
}
return true;
}
/**
* Get the crypt object
*
* @param string type type of encryption (aes, crypt or simple)
*
* @since 3.1
*
* @return Fabrik\Helpers\FCipher
*/
public static function getCrypt($type = 'simple')
{
return new FCipher($type);
}
/**
* Special case placeholder handling for repeat data. When something (usually an element plugin) is doing
* replacements for elements which are in the "same" repeat group, almost always they will want
* the value for the same repeat instance, not a comma seperated list of all the values. So (say)
* the upload element is creating a file path, for an upload element in a repeat group, of ...
* '/uploads/{repeat_table___userid}/', and there are 4 repeat instance, it doesn't want a path of ...
* '/uploads/34,45,94,103/', it just wants the one value from the same repeat count as the upload
* element. Or a calc element doing "return '{repeat_table___first_name} {repeat_table___last_name}';". Etc.
*
* Rather than make this a part of parseMessageForPlaceHolder, for now I'm making it a sperate function,
* which just handles this one very specific data replacement. Will look at merging it in with the main
* parsing once we have a better understanding of where / when / how to do it.
*
* @param string $msg Text to parse
* @param array $searchData Data to search for placeholders
* @param object $el Element model of the element which is doing the replacing
* @param int $repeatCounter Repeat instance
*
* @return string parsed message
*/
public function parseMessageForRepeats($msg, $searchData, $el, $repeatCounter)
{
if (strstr($msg, '{') && !empty($searchData))
{
$groupModel = $el->getGroupModel();
if ($groupModel->canRepeat())
{
$elementModels = $groupModel->getPublishedElements();
$formModel = $el->getFormModel();
foreach ($elementModels as $elementModel)
{
$repeatElName = $elementModel->getFullName(true, false);
foreach (array($repeatElName, $repeatElName . '_raw') as $tmpElName)
{
if (strstr($msg, '{' . $tmpElName . '}'))
{
if (array_key_exists($tmpElName, $searchData) && is_array($searchData[$tmpElName]) && array_key_exists($repeatCounter, $searchData[$tmpElName]))
{
$tmpVal = $searchData[$tmpElName][$repeatCounter];
if (is_array($tmpVal))
{
$tmpVal = implode(',', $tmpVal);
}
$msg = str_replace('{' . $tmpElName . '}', $tmpVal, $msg);
}
}
}
}
}
}
return $msg;
}
/**
* Iterates through string to replace every
* {placeholder} with posted data
*
* @param mixed $msg Text|Array to parse
* @param array $searchData Data to search for placeholders (default $_REQUEST)
* @param bool $keepPlaceholders If no data found for the place holder do we keep the {...} string in the
* message
* @param bool $addSlashes Add slashed to the text?
* @param object $theirUser User to use in replaceWithUserData (defaults to logged in user)
* @param bool $unsafe If true (default) will not replace certain placeholders like $jConfig_secret
* must not be shown to users
*
* @return string parsed message
*/
public function parseMessageForPlaceHolder($msg, $searchData = null, $keepPlaceholders = true, $addSlashes = false, $theirUser = null, $unsafe = true)
{
$returnType = is_array($msg) ? 'array' : 'string';
$messages = (array) $msg;
foreach ($messages as &$msg)
{
$this->parseAddSlashes = $addSlashes;
if (!($msg == '' || is_array($msg) || StringHelper::strpos($msg, '{') === false))
{
$msg = str_replace(array('%7B', '%7D'), array('{', '}'), $msg);
if (is_object($searchData))
{
$searchData = ArrayHelper::fromObject($searchData);
}
// Merge in request and specified search data
$f = JFilterInput::getInstance();
$post = $f->clean($_REQUEST, 'array');
$this->_searchData = is_null($searchData) ? $post : array_merge($post, $searchData);
// Enable users to use placeholder to insert session token
$this->_searchData['JSession::getFormToken'] = JSession::getFormToken();
// Replace with the user's data
$msg = self::replaceWithUserData($msg);
if (!is_null($theirUser))
{
// Replace with a specified user's data
$msg = self::replaceWithUserData($msg, $theirUser, 'your');
}
$msg = self::replaceWithGlobals($msg);
if (!$unsafe)
{
$msg = self::replaceWithUnsafe($msg);
$msg = self::replaceWithSession($msg);
}
$msg = preg_replace("/{}/", "", $msg);
// Replace {element name} with form data
$msg = preg_replace_callback("/{([^}\s]+(\|\|[\w|\s]+|<\?php.*\?>)*)}/i", array($this, 'replaceWithFormData'), $msg);
if (!$keepPlaceholders)
{
$msg = preg_replace("/{[^}\s]+}/i", '', $msg);
}
}
}
return $returnType === 'array' ? $messages : ArrayHelper::getValue($messages, 0, '');
}
/**
* Replace {varname} with request data (called from J content plugin)
*
* @param string &$msg String to parse
*
* @return void
*/
public function replaceRequest(&$msg)
{
static $request;
if (!is_array($request))
{
$request = array();
$f = JFilterInput::getInstance();
foreach ($_REQUEST as $k => $v)
{
if (is_string($v))
{
$request[$k] = $f->clean($v, 'CMD');
}
}
}
foreach ($request as $key => $val)
{
if (is_string($val))
{
// $$$ hugh - escape the key so preg_replace won't puke if key contains /
$key = str_replace('/', '\/', $key);
$msg = preg_replace("/\{$key\}/", $val, $msg);
}
}
}
/**
* Called from parseMessageForPlaceHolder to iterate through string to replace
* {placeholder} with user ($my) data
* AND
* {$their->var->email} placeholders
*
* @param string $msg Message to parse
* @param object $user Joomla user object
* @param string $prefix Search string to look for e.g. 'my' to look for {$my->id}
*
* @return string parsed message
*/
public static function replaceWithUserData($msg, $user = null, $prefix = 'my')
{
$app = JFactory::getApplication();
if (is_null($user))
{
$user = JFactory::getUser();
}
$user->levels = $user->getAuthorisedViewLevels();
if (is_object($user))
{
foreach ($user as $key => $val)
{
if (substr($key, 0, 1) != '_')
{
if (!is_object($val) && !is_array($val))
{
$msg = str_replace('{$' . $prefix . '->' . $key . '}', $val, $msg);
$msg = str_replace('{$' . $prefix . '->' . $key . '}', $val, $msg);
}
elseif (is_array($val))
{
$msg = str_replace('{$' . $prefix . '->' . $key . '}', implode(',', $val), $msg);
$msg = str_replace('{$' . $prefix . '->' . $key . '}', implode(',', $val), $msg);
}
}
}
}
/*
* $$$rob parse another users data into the string:
* format: is {$their->var->email} where var is the $app->input var to search for
* e.g url - index.php?owner=62 with placeholder {$their->owner->id}
* var should be an integer corresponding to the user id to load
*/
$matches = array();
preg_match('/{\$their-\>(.*?)}/', $msg, $matches);
foreach ($matches as $match)
{
$bits = explode('->', str_replace(array('{', '}'), '', $match));
if (count($bits) !== 3)
{
continue;
}
$userId = $app->input->getInt(ArrayHelper::getValue($bits, 1));
// things like user elements might be single entry arrays
if (is_array($userId))
{
$userId = array_pop($userId);
}
if (!empty($userId))
{
$user = JFactory::getUser($userId);
$val = $user->get(ArrayHelper::getValue($bits, 2));
$msg = str_replace($match, $val, $msg);
}
}
return $msg;
}
/**
* Called from parseMessageForPlaceHolder to iterate through string to replace
* {placeholder} with global data
*
* @param string $msg Message to parse
*
* @return string parsed message
*/
public static function replaceWithGlobals($msg)
{
$replacements = self::globalReplacements();
foreach ($replacements as $key => $value)
{
$msg = str_replace($key, $value, $msg);
}
return $msg;
}
/**
* Utility function for replacing language tags.
* {lang} - Joomla code for user's selected language, like en-GB
* {langtag} - as {lang} with with _ instead of -
* {shortlang} - first two letters of {lang}, like en
* {multilang} - multilang URL code
*
* @param string $msg Message to parse
*
* @return string parsed message
*/
public static function replaceWithLanguageTags($msg)
{
$replacements = self::langReplacements();
foreach ($replacements as $key => $value)
{
$msg = str_replace($key, $value, $msg);
}
return $msg;
}
/**
* Called from parseMessageForPlaceHolder to iterate through string to replace
* {placeholder} with unsafe data
*
* @param string $msg Message to parse
*
* @return string parsed message
*/
public static function replaceWithUnsafe($msg)
{
$replacements = self::unsafeReplacements();
foreach ($replacements as $key => $value)
{
$msg = str_replace($key, $value, $msg);
}
return $msg;
}
/**
* Called from parseMessageForPlaceHolder to iterate through string to replace
* {placeholder} with session data
*
* @param string $msg Message to parse
*
* @return string parsed message
*/
public static function replaceWithSession($msg)
{
if (strstr($msg, '{$session->'))
{
$session = JFactory::getSession();
$sessionData = array(
'id' => $session->getId(),
'token' => $session->get('session.token'),
'formtoken' => JSession::getFormToken()
);
foreach ($sessionData as $key => $value)
{
$msg = str_replace('{$session->' . $key . '}', $value, $msg);
}
$msg = preg_replace_callback(
'/{\$session-\>(.*?)}/',
function($matches) use ($session) {
$bits = explode(':', $matches[1]);
if (count($bits) > 1)
{
$sessionKey = $bits[1];
$nameSpace = $bits[0];
}
else
{
$sessionKey = $bits[0];
$nameSpace = 'default';
}
$val = $session->get($sessionKey, '', $nameSpace);
if (is_string($val))
{
return $val;
}
else if (is_numeric($val))
{
return (string) $val;
}
return '';
},
$msg
);
}
return $msg;
}
/**
* Get an associative array of replacements for 'unsafe' value, like $jConfig_secret, which we
* only want to use for stricty internal use that won't ever get shown to the user
*
* @return array
* @throws \Exception
*/
public static function unsafeReplacements()
{
$config = JFactory::getConfig();
$replacements = array(
'{$jConfig_absolute_path}' => JPATH_SITE,
'{$jConfig_secret}' => $config->get('secret')
);
return $replacements;
}
/**
* Get an associative array of replacements strings and values
*
* @return array
* @throws \Exception
*/
public static function globalReplacements()
{
$app = JFactory::getApplication();
$itemId = self::itemId();
$config = JFactory::getConfig();
$session = JFactory::getSession();
$token = $session->get('session.token');
$replacements = array(
'{$jConfig_live_site}' => COM_FABRIK_LIVESITE,
'{$jConfig_offset}' => $config->get('offset'),
'{$Itemid}' => $itemId,
'{$jConfig_sitename}' => $config->get('sitename'),
'{$jConfig_mailfrom}' => $config->get('mailfrom'),
'{where_i_came_from}' => $app->input->server->get('HTTP_REFERER', '', 'string'),
'{date}' => date('Ymd'),
'{year}' => date('Y'),
'{mysql_date}' => date('Y-m-d H:i:s'),
'{session.token}' => $token
);
foreach ($_SERVER as $key => $val)
{
if (!is_object($val) && !is_array($val))
{
$replacements['{$_SERVER->' . $key . '}'] = $val;
$replacements['{$_SERVER->' . $key . '}'] = $val;
}
}
if ($app->isClient('administrator'))
{
$replacements['{formview}'] = 'task=form.view';
$replacements['{listview}'] = 'task=list.view';
$replacements['{detailsview}'] = 'task=details.view';
}
else
{
$replacements['{formview}'] = 'view=form';
$replacements['{listview}'] = 'view=list';
$replacements['{detailsview}'] = 'view=details';
}
return array_merge($replacements, self::langReplacements());
}
/**
* Returns array of language tag replacements
*
* @return array
*/
public static function langReplacements()
{
$langtag = JFactory::getLanguage()->getTag();
$lang = str_replace('-', '_', $langtag);
$shortlang = explode('_', $lang);
$shortlang = $shortlang[0];
$multilang = Worker::getMultiLangURLCode();
$replacements = array(
'{lang}' => $lang,
'{langtag}' => $langtag,
'{multilang}' => $multilang,
'{shortlang}' => $shortlang,
);
return $replacements;
}
/**
* Called from parseMessageForPlaceHolder to iterate through string to replace
* {placeholder} with posted data
*
* @param string $matches Placeholder e.g. {placeholder}
*
* @return string posted data that corresponds with placeholder
*/
protected function replaceWithFormData($matches)
{
// Merge any join data key val pairs down into the main data array
$joins = ArrayHelper::getValue($this->_searchData, 'join', array());
foreach ($joins as $k => $data)
{
foreach ($data as $k => $v)
{
/*
* Only replace if we haven't explicitly set the key in _searchData.
* Otherwise, calc element in repeat group uses all repeating groups values rather than the
* current one that the plugin sets when it fire its Ajax request.
*/
if (!array_key_exists($k, $this->_searchData))
{
$this->_searchData[$k] = $v;
}
}
}
$match = $matches[0];
$orig = $match;
// Strip the {}
$match = StringHelper::substr($match, 1, StringHelper::strlen($match) - 2);
/* $$$ hugh - added dbprefix substitution
* Not 100% if we should do this on $match before copying to $orig, but for now doing it
* after, so we don't potentially disclose dbprefix if no substitution found.
*/
$config = JFactory::getConfig();
$prefix = $config->get('dbprefix');
$match = str_replace('#__', $prefix, $match);
// $$$ rob test this format searchvalue||defaultsearchvalue
$bits = explode('||', $match);
if (count($bits) == 2)
{
$match = self::parseMessageForPlaceHolder('{' . $bits[0] . '}', $this->_searchData, false);
if (in_array($match, array('', '<ul></ul>', '<ul><li></li></ul>')))
{
// experiment with eval'ed code in defaults
if (strstr($bits[1], '<?php'))
{
$code = preg_replace('/^<\?php(.*)(\?>)$/s', '$1', $bits[1]);
$bits[1] = @eval($code);
}
return $bits[1] !== '' ? $bits[1] : $orig;
}
else
{
return $match !== '' ? $match : $orig;
}
}
$match = preg_replace("/ /", "_", $match);
if (!strstr($match, '.'))
{
// For some reason array_key_exists wasn't working for nested arrays??
$aKeys = array_keys($this->_searchData);
// Remove the table prefix from the post key
$aPrefixFields = array();
for ($i = 0; $i < count($aKeys); $i++)
{
$aKeyParts = explode('___', $aKeys[$i]);
if (count($aKeyParts) == 2)
{
$tablePrefix = array_shift($aKeyParts);
$field = array_pop($aKeyParts);
$aPrefixFields[$field] = $tablePrefix;
}
}
if (array_key_exists($match, $aPrefixFields))
{
$match = $aPrefixFields[$match] . '___' . $match;
}
// Test to see if the made match is in the post key arrays
$found = in_array($match, $aKeys, true);
if ($found)
{
// Get the post data
$match = $this->_searchData[$match];
if (is_array($match))
{
$newMatch = '';
// Deal with radio boxes etc. inside repeat groups
foreach ($match as $m)
{
if (is_array($m))
{
$newMatch .= ',' . implode(',', $m);
}
else
{
$newMatch .= ',' . $m;
}
}
$match = StringHelper::ltrim($newMatch, ',');
}
}
else
{
$match = '';
}
}
else
{
// Could be looking for URL field type e.g. for $_POST[url][link] the match text will be url.link
$aMatch = explode('.', $match);
$aPost = $this->_searchData;
foreach ($aMatch as $sPossibleArrayKey)
{
if (is_array($aPost))
{
if (!isset($aPost[$sPossibleArrayKey]))
{
return $orig;
}
else
{
$aPost = $aPost[$sPossibleArrayKey];
}
}
}
$match = $aPost;
$found = true;
}
if ($this->parseAddSlashes)
{
$match = htmlspecialchars($match, ENT_QUOTES, 'UTF-8');
}
return $found ? $match : $orig;
}
/**
* Internal function to recursive scan directories
*
* @param string $imagePath Image path
* @param string $folderPath Path to scan
* @param string &$folders Root path of this folder
* @param array &$images Value array of all existing folders
* @param array $aFolderFilter Value array of all existing images
* @param bool $makeOptions Make options out for the results
*
* @return void
*/
public static function readImages($imagePath, $folderPath, &$folders, &$images, $aFolderFilter, $makeOptions = true)
{
$imgFiles = self::fabrikReadDirectory($imagePath, '.', false, false, $aFolderFilter);
foreach ($imgFiles as $file)
{
$ff_ = $folderPath . $file . '/';
$ff = $folderPath . $file;
$i_f = $imagePath . '/' . $file;
if (is_dir($i_f) && $file != 'CVS' && $file != '.svn')
{
if (!in_array($file, $aFolderFilter))
{
$folders[] = JHTML::_('select.option', $ff_);
self::readImages($i_f, $ff_, $folders, $images, $aFolderFilter);
}
}
elseif (preg_match('/bmp|gif|jpg|png/i', $file) && is_file($i_f))
{
// Leading / we don't need
$imageFile = StringHelper::substr($ff, 1);
$images[$folderPath][] = $makeOptions ? JHTML::_('select.option', $imageFile, $file) : $file;
}
}
}
/**
* Utility function to read the files in a directory
*
* @param string $path The file system path
* @param string $filter A filter for the names
* @param bool $recurse Recurse search into sub-directories
* @param bool $fullPath True if to prepend the full path to the file name
* @param array $aFolderFilter Folder names not to recurse into
* @param bool $foldersOnly Return a list of folders only (true)
*
* @return array of file/folder names
*/
public static function fabrikReadDirectory($path, $filter = '.', $recurse = false, $fullPath = false, $aFolderFilter = array(),
$foldersOnly = false)
{
$arr = array();
if (!@is_dir($path))
{
return $arr;
}
$handle = opendir($path);
while ($file = readdir($handle))
{
$dir = JPath::clean($path . '/' . $file);
$isDir = is_dir($dir);
if ($file != "." && $file != "..")
{
if (preg_match("/$filter/", $file))
{
if (($isDir && $foldersOnly) || !$foldersOnly)
{
if ($fullPath)
{
$arr[] = trim(JPath::clean($path . '/' . $file));
}
else
{
$arr[] = trim($file);
}
}
}
$goDown = true;
if ($recurse && $isDir)
{
foreach ($aFolderFilter as $sFolderFilter)
{
if (strstr($dir, $sFolderFilter))
{
$goDown = false;
}
}
if ($goDown)
{
$arr2 = self::fabrikReadDirectory($dir, $filter, $recurse, $fullPath, $aFolderFilter, $foldersOnly);
$arrDiff = array_diff($arr, $arr2);
$arr = array_merge($arrDiff);
}
}
}
}
closedir($handle);
asort($arr);
return $arr;
}
/**
* Joomfish translations don't seem to work when you do an ajax call
* it seems to load the geographical location language rather than the selected lang
* so for ajax calls that need to use jf translated text we need to get the current lang and
* send it to the js code which will then append the lang=XX to the ajax querystring
*
* Renamed to getShortLang as we don't support Joomfish any more
*
* @since 2.0.5
*
* @return string first two letters of lang code - e.g. nl from 'nl-NL'
*/
public static function getShortLang()
{
$lang = JFactory::getLanguage();
$lang = explode('-', $lang->getTag());
return array_shift($lang);
}
/**
* If J! multiple languages is enabled, return the URL language code for the currently selected language, which is
* set by the admin in the 'content languages'. If not multi lang, return false;
*
* @return boolean || string
*/
public static function getMultiLangURLCode()
{
$multiLang = false;
if (JLanguageMultilang::isEnabled())
{
$lang = JFactory::getLanguage()->getTag();
$languages = JLanguageHelper::getLanguages();
foreach ($languages as $language)
{
if ($language->lang_code === $lang)
{
$multiLang = $language->sef;
break;
}
}
}
return $multiLang;
}
/**
* Get the content filter used both in form and admin pages for content filter
* takes values from J content filtering options
*
* @return array (bool should the filter be used, object the filter to use)
*/
public static function getContentFilter()
{
$filter = false;
// Filter settings
jimport('joomla.application.component.helper');
// Get Config and Filters in Joomla 2.5
$config = JComponentHelper::getParams('com_config');
$filters = $config->get('filters');
// If no filter data found, get from com_content (Joomla 1.6/1.7 sites)
if (empty($filters))
{
$contentParams = JComponentHelper::getParams('com_content');
$filters = $contentParams->get('filters');
}
$user = JFactory::getUser();
$userGroups = JAccess::getGroupsByUser($user->get('id'));
$blackListTags = array();
$blackListAttributes = array();
$whiteListTags = array();
$whiteListAttributes = array();
$whiteList = false;
$blackList = false;
$unfiltered = false;
// Cycle through each of the user groups the user is in.
// Remember they are include in the Public group as well.
foreach ($userGroups AS $groupId)
{
// May have added a group by not saved the filters.
if (!isset($filters->$groupId))
{
continue;
}
// Each group the user is in could have different filtering properties.
$filterData = $filters->$groupId;
$filterType = StringHelper::strtoupper($filterData->filter_type);
if ($filterType == 'NH')
{
// Maximum HTML filtering.
}
elseif ($filterType == 'NONE')
{
// No HTML filtering.
$unfiltered = true;
}
else
{
// Black or white list.
// Pre-process the tags and attributes.
$tags = explode(',', $filterData->filter_tags);
$attributes = explode(',', $filterData->filter_attributes);
$tempTags = array();
$tempAttributes = array();
foreach ($tags as $tag)
{
$tag = trim($tag);
if ($tag)
{
$tempTags[] = $tag;
}
}
foreach ($attributes as $attribute)
{
$attribute = trim($attribute);
if ($attribute)
{
$tempAttributes[] = $attribute;
}
}
// Collect the black or white list tags and attributes.
// Each list is cumulative.
if ($filterType == 'BL' || $filterType == 'CBL')
{
$blackList = true;
$blackListTags = array_merge($blackListTags, $tempTags);
$blackListAttributes = array_merge($blackListAttributes, $tempAttributes);
}
elseif ($filterType == 'WL')
{
$whiteList = true;
$whiteListTags = array_merge($whiteListTags, $tempTags);
$whiteListAttributes = array_merge($whiteListAttributes, $tempAttributes);
}
}
}
// Remove duplicates before processing (because the black list uses both sets of arrays).
$blackListTags = array_unique($blackListTags);
$blackListAttributes = array_unique($blackListAttributes);
$whiteListTags = array_unique($whiteListTags);
$whiteListAttributes = array_unique($whiteListAttributes);
// Unfiltered assumes first priority.
if ($unfiltered)
{
$doFilter = false;
// Don't apply filtering.
}
else
{
$doFilter = true;
// Black lists take second precedence.
if ($blackList)
{
// Remove the white-listed attributes from the black-list.
$tags = array_diff($blackListTags, $whiteListTags);
$filter = JFilterInput::getInstance($tags, array_diff($blackListAttributes, $whiteListAttributes), 1, 1);
}
// White lists take third precedence.
elseif ($whiteList)
{
// Turn off xss auto clean
$filter = JFilterInput::getInstance($whiteListTags, $whiteListAttributes, 0, 0, 0);
}
// No HTML takes last place.
else
{
$filter = JFilterInput::getInstance();
}
}
return array($doFilter, $filter);
}
/**
* Clear PHP errors prior to running eval'd code
*
* @return void
*/
public static function clearEval()
{
/**
* "Clear" PHP's errors. NOTE that error_get_last() will still return non-null after this
* if there were any errors, but $error['message'] will be empty. See comment in logEval()
* below for details.
*/
if (version_compare(PHP_VERSION, '7.0.0') >= 0) {
error_clear_last();
}
else
{
@trigger_error("");
}
}
/**
* Raise a J Error notice if the eval'd result is false and there is a error
*
* @param mixed $val Evaluated result
* @param string $msg Error message, should contain %s as we sprintf in the error_get_last()'s message property
*
* @return void
*/
public static function logEval($val, $msg)
{
/*
if ($val !== false)
{
return;
}
*/
$error = error_get_last();
/**
* $$$ hugh - added check for 'message' being empty, so we can do ..
* @trigger_error('');
* ... prior to eval'ing code if we want to "clear" anything pitched prior
* to the eval. For instance, in the PHP validation plugin. If we don't "clear"
* the errors before running the eval'd validation code, we end up reporting any
* warnings or notices pitched in our code prior to the validation running, which
* can be REALLY confusing. After a trigger_error(), error_get_last() won't return null,
* but 'message' will be empty.
*/
if (is_null($error) || empty($error['message']))
{
// No error set (eval could have actually returned false as a correct value)
return;
}
$enqMsgType = 'error';
$indentHTML = '<br/> Debug: ';
$errString = Text::_('COM_FABRIK_EVAL_ERROR_USER_WARNING');
// Give a technical error message to the developer
if (version_compare(phpversion(), '5.2.0', '>=') && $error && is_array($error))
{
$errString .= $indentHTML . sprintf($msg, $error['message']);
}
else
{
$errString .= $indentHTML . sprintf($msg, "unknown error - php version < 5.2.0");
}
self::logError($errString, $enqMsgType);
}
/**
* Raise a J Error notice if in dev mode or log a J error otherwise
*
* @param string $errString Message to display / log
* @param string $msgType Joomla enqueueMessage message type e.g. 'error', 'warning' etc.
*
* @return void
*/
public static function logError($errString, $msgType)
{
if (Html::isDebug())
{
$app = JFactory::getApplication();
$app->enqueueMessage($errString, $msgType);
}
else
{
switch ($msgType)
{
case 'message':
$priority = JLog::INFO;
break;
case 'warning':
$priority = JLog::WARNING;
break;
case 'error':
default:
$priority = JLog::ERROR;
break;
}
JLog::add($errString, $priority, 'com_fabrik');
}
}
/**
* Log to table jos_fabrik_logs
*
* @param string $type E.g. 'fabrik.fileupload.download'
* @param mixed $msg Array/object/string
* @param bool $jsonEncode Should we json encode the message?
*
* @return void
*/
public static function log($type, $msg, $jsonEncode = true)
{
if ($jsonEncode)
{
$msg = json_encode($msg);
}
$log = FabTable::getInstance('log', 'FabrikTable');
$log->message_type = $type;
$log->message = $msg;
$log->store();
}
/**
* Get a database object
*
* Returns the global {@link JDatabase} object, only creating it
* if it doesn't already exist.
*
* @param bool $loadJoomlaDb Force (if true) the loading of the main J database,
* needed in admin to connect to J db whilst still using fab db drivers "{package}"
* replacement text
*
* @param mixed $cnnId If null then loads the fabrik default connection, if an int then loads the
* specified connection by its id
*
* @return JDatabaseDriver object
*/
public static function getDbo($loadJoomlaDb = false, $cnnId = null)
{
$sig = (int) $loadJoomlaDb . '.' . $cnnId;
if (!self::$database)
{
self::$database = array();
self::$database = array();
}
if (!array_key_exists($sig, self::$database))
{
JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_fabrik/tables');
$conf = JFactory::getConfig();
if (!$loadJoomlaDb)
{
$cnModel = JModelLegacy::getInstance('Connection', 'FabrikFEModel');
$cn = $cnModel->getConnection($cnnId);
$host = $cn->host;
$user = $cn->user;
$password = $cn->password;
$database = $cn->database;
}
else
{
$host = $conf->get('host');
$user = $conf->get('user');
$password = $conf->get('password');
$database = $conf->get('db');
}
$dbPrefix = $conf->get('dbprefix');
$driver = $conf->get('dbtype');
// Test for swapping db table names
$driver .= '_fab';
$options = array('driver' => $driver, 'host' => $host, 'user' => $user, 'password' => $password, 'database' => $database,
'prefix' => $dbPrefix);
$version = new JVersion;
self::$database[$sig] = $version->RELEASE > 2.5 ? JDatabaseDriver::getInstance($options) : JDatabase::getInstance($options);
Worker::bigSelects(self::$database[$sig]);
}
return self::$database[$sig];
}
/**
* $$$ hugh - testing doing bigSelects stuff here
* Reason being, some folk on shared hosting plans with very restrictive MySQL
* setups are hitting the 'big selects' problem on Fabrik internal queries, not
* just on their List specific queries. So we need to apply 'big selects' to our
* default connection as well, essentially enabling it for ALL queries we do.
*
* @param JDatabaseDriver $fabrikDb
*
* @return void
*/
public static function bigSelects($fabrikDb)
{
$fbConfig = JComponentHelper::getParams('com_fabrik');
if ($fbConfig->get('enable_big_selects', 0) == '1')
{
/**
* Use of OPTION in SET deprecated from MySQL 5.1. onward
* http://www.fabrikar.com/forums/index.php?threads/enable-big-selects-error.39463/#post-198293
* NOTE - technically, using verison_compare on MySQL version could fail, if it's a "gamma"
* release, which PHP desn't grok!
*/
if (version_compare($fabrikDb->getVersion(), '5.1.0', '>='))
{
$fabrikDb->setQuery("SET SQL_BIG_SELECTS=1, GROUP_CONCAT_MAX_LEN=10240");
}
else
{
$fabrikDb->setQuery("SET OPTION SQL_BIG_SELECTS=1, GROUP_CONCAT_MAX_LEN=10240");
}
try
{
$fabrikDb->execute();
} catch (\Exception $e)
{
// Fail silently
}
}
}
/**
* Helper function get get a connection
*
* @param mixed $item A list table or connection id
*
* @since 3.0b
*
* @return FabrikFEModelConnection connection
*/
public static function getConnection($item = null)
{
$app = JFactory::getApplication();
$input = $app->input;
$jForm = $input->get('jform', array(), 'array');
if (is_object($item))
{
$item = is_null($item->connection_id) ? ArrayHelper::getValue($jForm, 'connection_id', -1) : $item->connection_id;
}
$connId = (int) $item;
if (!self::$connection)
{
self::$connection = array();
}
if (!array_key_exists($connId, self::$connection))
{
$connectionModel = JModelLegacy::getInstance('connection', 'FabrikFEModel');
$connectionModel->setId($connId);
if ($connId === -1)
{
// -1 for creating new table
$connectionModel->loadDefaultConnection();
$connectionModel->setId($connectionModel->getConnection()->id);
}
$connectionModel->getConnection();
self::$connection[$connId] = $connectionModel;
}
return self::$connection[$connId];
}
/**
* Get the plugin manager
*
* @since 3.0b
*
* @return FabrikFEModelPluginmanager Plugin manager
*/
public static function getPluginManager()
{
if (!self::$pluginManager)
{
self::$pluginManager = JModelLegacy::getInstance('Pluginmanager', 'FabrikFEModel');
}
return self::$pluginManager;
}
/**
* Takes a string which may or may not be json and returns either string/array/object
* will also turn valGROUPSPLITTERval2 to array
*
* @param string $data Json encoded string
* @param bool $toArray Force data to be an array
* @param bool $emptyish Set to false to return an empty array if $data is an empty string, instead of an
* emptyish (one empty string entry) array
*
* @return mixed data
*/
public static function JSONtoData($data, $toArray = false, $emptyish = true)
{
if (is_string($data))
{
if (!strstr($data, '{'))
{
// Was messing up date rendering @ http://www.podion.eu/dev2/index.php/2011-12-19-10-33-59/actueel
// return $toArray ? (array) $data : $data;
}
// Repeat elements are concatenated with the GROUPSPLITTER - convert to json string before continuing.
if (strstr($data, GROUPSPLITTER))
{
$data = json_encode(explode(GROUPSPLITTER, $data));
}
/* half hearted attempt to see if string is actually json or not.
* issue was that if you try to decode '000123' its turned into '123'
*/
if (strstr($data, '{') || strstr($data, '['))
{
$json = json_decode($data);
// Only works in PHP5.3
// $data = (json_last_error() == JSON_ERROR_NONE) ? $json : $data;
if (is_null($json))
{
/*
* if coming back from a failed validation - the json string may have been htmlspecialchars_encoded in
* the form model getGroupView method
*/
$json = json_decode(stripslashes(htmlspecialchars_decode($data, ENT_QUOTES)));
}
$data = is_null($json) ? $data : $json;
}
// If $data was an empty string and "emptyish" is not set, we want an empty array, not an array with one empty string
if ($toArray && !$emptyish && $data === '')
{
$data = array();
}
}
$data = $toArray ? (array) $data : $data;
return $data;
}
/**
* Test if a string is a compatible date
*
* @param string $d Date to test
* @param bool $notNull don't allow null / empty dates
*
* @return bool
*/
public static function isNullDate($d)
{
$db = self::getDbo(true);
$aNullDates = array('0000-00-000000-00-00', '0000-00-00 00:00:00', '0000-00-00', '', $db->getNullDate());
return in_array($d, $aNullDates);
}
/**
* Test if a string is a compatible date
*
* @param string $d Date to test
* @param bool $notNull don't allow null / empty dates
*
* @return bool
*/
public static function isDate($d, $notNull = true)
{
// Catch for ','
if (strlen($d) < 2)
{
return false;
}
if ($notNull && self::isNullDate($d))
{
return false;
}
$cerl = error_reporting ();
error_reporting (0);
try
{
$dt = new DateTime($d);
} catch (\Exception $e)
{
error_reporting ($cerl);
self::clearEval();
return false;
}
error_reporting ($cerl);
return true;
}
public static function addMonthsInterval($months, DateTime $date)
{
$next = new DateTime($date->format('d-m-Y H:i:s'));
$next->modify('last day of +' . $months . ' month');
if ($date->format('d') > $next->format('d'))
{
return $date->diff($next);
}
else
{
return new DateInterval('P' . $months . 'M');
}
}
public static function addMonths($months, DateTime $date)
{
return $date->add(self::addMonthsInterval($months, $date));
}
/**
* Get a user's TZ offset in MySql format, suitable for CONVERT_TZ
*
* @param int userId userid or null (use logged on user if null)
*
* @return string symbolic timezone name (America/Chicago)
*/
public static function getUserTzOffsetMySql($userId = null)
{
$tz = self::getUserTzName($userId);
$tz = new \DateTimeZone($tz);
$date = new \DateTime("now", $tz);
$offset = $tz->getOffset($date) . ' seconds';
$dateOffset = clone $date;
$dateOffset->sub(\DateInterval::createFromDateString($offset));
$interval = $dateOffset->diff($date);
return $interval->format('%R%H:%I');
}
/**
* Get a user's TZ offset in seconds
*
* @param int userId userid or null (use logged on user if null)
*
* @return int seconds offset
*/
public static function getUserTzOffset($userId = null)
{
$tz = self::getUserTzName($userId);
$tz = new \DateTimeZone($tz);
$date = new \DateTime("now", $tz);
return $tz->getOffset($date);
}
/**
* Get a user's TZ name
*
* @param int userId userid or null (use logged on user if null)
*
* @return string symbolic timezone name (America/Chicago)
*/
public static function getUserTzName($userId = null)
{
if (empty($userId))
{
$user = JFactory::getUser();
}
else
{
$user = JFactory::getUser($userId);
}
$config = JFactory::getConfig();
$tz = $user->getParam('timezone', $config->get('offset'));
return $tz;
}
/**
* See if data is JSON or not.
*
* @param mixed $data Date to test
* @params bool $quotedString should we treat a single quoted string as JSON
* @since 3.0.6
*
* @return bool
*/
public static function isJSON($data, $quotedString = true)
{
if (!is_string($data))
{
return false;
}
if (is_numeric($data))
{
return false;
}
if (!$quotedString)
{
$data = trim($data, '"');
}
return json_decode($data) !== null;
}
/**
* Is the email really an email (more strict than JMailHelper::isEmailAddress())
*
* @param string $email Email address
* @param bool $sms test for SMS phone number instead of email, default false
*
* @since 3.0.4
*
* @return bool
*/
public static function isEmail($email, $sms = false)
{
if ($sms)
{
return self::isSMS($email);
}
$conf = JFactory::getConfig();
$mailer = $conf->get('mailer');
if ($mailer === 'mail')
{
// Sendmail and Joomla isEmailAddress don't use the same conditions
return (JMailHelper::isEmailAddress($email) && JMail::ValidateAddress($email));
}
return JMailHelper::isEmailAddress($email);
}
/**
* Is valid SMS number format
* This is just a stub which return true for now!
*
* @param string $sms SMS number
*
* @since 3.4.0
*
* @return bool
*/
public static function isSMS($sms)
{
return true;
}
/**
* Function to send an email
*
* @param string $from From email address
* @param string $fromName From name
* @param mixed $recipient Recipient email address(es)
* @param string $subject email subject
* @param string $body Message body
* @param boolean $mode false = plain text, true = HTML
* @param mixed $cc CC email address(es)
* @param mixed $bcc BCC email address(es)
* @param mixed $attachment Attachment file name(s)
* @param mixed $replyTo Reply to email address(es)
* @param mixed $replyToName Reply to name(s)
* @param array $headers Optional custom headers, assoc array keyed by header name
*
* @return boolean True on success
*
* @since 11.1
*/
public static function sendMail($from, $fromName, $recipient, $subject, $body, $mode = false,
$cc = null, $bcc = null, $attachment = null, $replyTo = null, $replyToName = null, $headers = array())
{
// do a couple of tweaks to improve spam scores
// Get a JMail instance
$mailer = JFactory::getMailer();
// If html, make sure there's an <html> tag
if ($mode)
{
if (!stristr($body, '<html>'))
{
$body = '<html>' . $body . '</html>';
}
}
/**
* if simple single email recipient with no name part, fake out name part to avoid TO_NO_BKRT hit in spam filters
* (don't do it for sendmail, as sendmail only groks simple emails in To header!)
*/
$recipientName = '';
if ($mailer->Mailer !== 'sendmail' && is_string($recipient) && !strstr($recipient, '<'))
{
$recipientName = $recipient;
}
$mailer->setSubject($subject);
$mailer->setBody($body);
$mailer->Encoding = 'base64';
// Are we sending the email as HTML?
$mailer->isHtml($mode);
try
{
$mailer->addRecipient($recipient, $recipientName);
}
catch (\Exception $e)
{
self::log('fabrik.helper.sendmail.error', 'Exception in addRecipient: ' . $e->getMessage(), false);
return false;
}
try
{
$mailer->addCc($cc);
}
catch (\Exception $e)
{
self::log('fabrik.helper.sendmail.warning', 'Exception in addCc: ' . $e->getMessage());
// not sure if we should bail if Cc is bad, for now just soldier on
}
try
{
$mailer->addBcc($bcc);
}
catch (\Exception $e)
{
self::log('fabrik.helper.sendmail.warning', 'Exception in addBcc: ' . $e->getMessage(), false);
// not sure if we should bail if Bcc is bad, for now just soldier on
}
if (!empty($attachment))
{
try
{
$mailer->addAttachment($attachment);
}
catch (\Exception $e)
{
self::log('fabrik.helper.sendmail.warning', 'Exception in addAttachment: ' . $e->getMessage(), false);
// most likely file didn't exist, ignore
}
}
$autoReplyTo = false;
// Take care of reply email addresses
if (is_array($replyTo))
{
$numReplyTo = count($replyTo);
for ($i = 0; $i < $numReplyTo; $i++)
{
try
{
$mailer->addReplyTo($replyTo[$i], $replyToName[$i]);
}
catch (\Exception $e)
{
self::log('fabrik.helper.sendmail.warning', 'Exception in addReplyTo: ' . $e->getMessage(), false);
// carry on
}
}
}
elseif (isset($replyTo))
{
try
{
$mailer->addReplyTo($replyTo, $replyToName);
}
catch (\Exception $e)
{
self::log('fabrik.helper.sendmail.warning', 'Exception in addReplyTo: ' . $e->getMessage(), false);
// carry on
}
}
else
{
$autoReplyTo = true;
}
try
{
$mailer->setSender(array($from, $fromName, $autoReplyTo));
}
catch (\Exception $e)
{
self::log('fabrik.helper.sendmail.error', 'Exception in setSender: ' . $e->getMessage(), false);
return false;
}
/**
* Set the plain text AltBody, which forces the PHP mailer class to make this
* a multipart MIME type, with an alt body for plain text. If we don't do this,
* the default behavior is to send it as just text/html, which causes spam filters
* to downgrade it.
* @@@trob: insert \n before <br to keep newlines(strip_tag may then strip <br> or <br /> etc, decode html
*/
if ($mode)
{
$body = str_ireplace(array("<br />","<br>","<br/>"), "\n<br />", $body);
$body = html_entity_decode($body);
$mailer->AltBody = JMailHelper::cleanText(strip_tags($body));
}
foreach ($headers as $headerName => $headerValue) {
$mailer->addCustomHeader($headerName, $headerValue);
}
$config = JComponentHelper::getParams('com_fabrik');
if ($config->get('verify_peer', '1') === '0')
{
$mailer->SMTPOptions = array(
'ssl' =>
array(
'verify_peer' => false,
'verify_peer_name' => false,
'allow_self_signed' => true
)
);
}
try
{
$ret = $mailer->Send();
}
catch (\Exception $e)
{
self::log('fabrik.helper.sendmail.error', 'Exception in Send: ' . $e->getMessage(), false);
return false;
}
// if ACY mailing is installed, it returns an exception (!) rather than false
if (get_parent_class($ret) === 'Exception')
{
self::log('fabrik.helper.sendmail.error', 'Exception in Send: ' . $ret->getMessage(), false);
$ret = false;
}
return $ret;
}
/**
* Get a JS go back action e.g 'onclick="history.back()"
*
* @return string
*/
public static function goBackAction()
{
jimport('joomla.environment.browser');
$uri = JUri::getInstance();
$url = filter_var(ArrayHelper::getValue($_SERVER, 'HTTP_REFERER'), FILTER_SANITIZE_URL);
if ($uri->getScheme() === 'https')
{
$goBackAction = 'onclick="parent.location=\'' . $url . '\'"';
}
else
{
$goBackAction = 'onclick="parent.location=\'' . $url . '\'"';
}
return $goBackAction;
}
/**
* Attempt to find the active menu item id - Only for front end
*
* - First checked $listId for menu items
* - Then checks if itemId in $input
* - Finally checked active menu item
*
* @param int $listId List id to attempt to get the menu item id for the list.
*
* @return mixed NULL if nothing found, int if menu item found
*/
public static function itemId($listId = null)
{
static $listIds = array();
$app = JFactory::getApplication();
if (!$app->isAdmin())
{
// Attempt to get Itemid from possible list menu item.
if (!is_null($listId))
{
if (!array_key_exists($listId, $listIds))
{
$db = JFactory::getDbo();
$myLanguage = JFactory::getLanguage();
$myTag = $myLanguage->getTag();
$qLanguage = !empty($myTag) ? ' AND ' . $db->q($myTag) . ' = ' . $db->qn('m.language') : '';
$query = $db->getQuery(true);
$query->select('m.id AS itemId')->from('#__extensions AS e')
->leftJoin('#__menu AS m ON m.component_id = e.extension_id')
->where('e.name = "com_fabrik" and e.type = "component" and m.link LIKE "%listid=' . $listId . '"' . $qLanguage);
$db->setQuery($query);
if ($itemId = $db->loadResult())
{
$listIds[$listId] = $itemId;
}
else{
$listIds[$listId] = false;
}
}
else{
if ($listIds[$listId] !== false)
{
return $listIds[$listId];
}
}
}
$itemId = (int) $app->input->getInt('itemId');
if ($itemId !== 0)
{
return $itemId;
}
$menus = $app->getMenu();
$menu = $menus->getActive();
if (is_object($menu))
{
return $menu->id;
}
}
return null;
}
/**
* Attempt to get a variable first from the menu params (if they exists) if not from request
*
* @param string $name Param name
* @param mixed $val Default
* @param bool $mambot If set to true menu params ignored
* @param string $priority Defaults that menu priorities override request - set to 'request'
* to inverse this priority
* @param array $opts Options 'listid' -> if priority = menu then the menu list id must
* match this value to use the menu param.
*
* @return string
*/
public static function getMenuOrRequestVar($name, $val = '', $mambot = false, $priority = 'menu', $opts = array())
{
$app = JFactory::getApplication();
$input = $app->input;
if ($priority === 'menu')
{
$val = $input->get($name, $val, 'string');
if (!$app->isAdmin())
{
if (!$mambot)
{
$menus = $app->getMenu();
$menu = $menus->getActive();
if (is_object($menu))
{
$match = true;
if (array_key_exists('listid', $opts)) {
$menuListId = ArrayHelper::getValue($menu->query, 'listid', '');
$checkListId = ArrayHelper::getValue($opts, 'listid', $menuListId);
$match = (int) $menuListId === (int) $checkListId;
}
else if (array_key_exists('formid', $opts)) {
$menuFormId = ArrayHelper::getValue($menu->query, 'formid', '');
$checkFormId = ArrayHelper::getValue($opts, 'formid', $menuFormId);
$match = (int) $menuFormId === (int) $checkFormId;
}
if ($match)
{
$val = $menu->params->get($name, $val);
}
}
}
}
}
else
{
if (!$app->isAdmin())
{
$menus = $app->getMenu();
$menu = $menus->getActive();
// If there is a menu item available AND the view is not rendered in a content plugin
if (is_object($menu) && !$mambot)
{
$match = true;
if (array_key_exists('listid', $opts)) {
$menuListId = ArrayHelper::getValue($menu->query, 'listid', '');
$checkListId = ArrayHelper::getValue($opts, 'listid', $menuListId);
$match = (int) $menuListId === (int) $checkListId;
}
else if (array_key_exists('formid', $opts)) {
$menuFormId = ArrayHelper::getValue($menu->query, 'formid', '');
$checkFormId = ArrayHelper::getValue($opts, 'formid', $menuFormId);
$match = (int) $menuFormId === (int) $checkFormId;
}
if ($match)
{
$val = $menu->params->get($name, $val);
}
}
}
$val = $input->get($name, $val, 'string');
}
return $val;
}
/**
* Access control function for determining if the user can perform
* a designated function on a specific row
*
* @param object $params Item params to test
* @param object $row Data
* @param string $col Access control setting to compare against
*
* @return mixed - if ACL setting defined here return bool, otherwise return -1 to continue with default acl
* setting
*/
public static function canUserDo($params, $row, $col)
{
if (!is_null($row))
{
$app = JFactory::getApplication();
$input = $app->input;
$user = JFactory::getUser();
$userCol = $params->get($col, '');
if ($userCol != '')
{
$userCol = StringHelper::safeColNameToArrayKey($userCol);
if ((is_array($row) && !array_key_exists($userCol, $row)) || (is_object($row) && !isset($row->{$userCol})))
{
return false;
}
else
{
$userColRaw = $userCol . '_raw';
if ((is_array($row) && array_key_exists($userColRaw, $row)) || (is_object($row) && isset($row->{$userColRaw})))
{
$userCol .= '_raw';
}
$myId = $user->get('id');
// -1 for menu items that link to their own records
$userColVal = is_array($row) ? $row[$userCol] : $row->$userCol;
// User element stores as object
if (is_object($userColVal))
{
$userColVal = ArrayHelper::fromObject($userColVal);
}
// Could be coming back from a failed validation in which case val might be an array
if (is_array($userColVal))
{
$userColVal = array_shift($userColVal);
}
if (empty($userColVal) && empty($myId))
{
return false;
}
if (intVal($userColVal) === intVal($myId) || $input->get('rowid') == -1)
{
return true;
}
}
}
}
return -1;
}
/**
* Can Fabrik render PDF - required the DOMPDF library to be installed in Joomla libraries folder
*
* @param bool $puke throw an exception if can't
*
* @throws RuntimeException
*
* @return bool
*/
public static function canPdf($puke = true)
{
$config = \JComponentHelper::getParams('com_fabrik');
if ($config->get('fabrik_pdf_lib', 'dompdf') === 'dompdf')
{
$file = COM_FABRIK_LIBRARY . '/vendor/dompdf/dompdf/composer.json';
}
else
{
$file = COM_FABRIK_LIBRARY . '/vendor/mpdf/mpdf/composer.json';
}
if (!JFile::exists($file))
{
if ($puke)
{
throw new RuntimeException(Text::_('COM_FABRIK_NOTICE_DOMPDF_NOT_FOUND'));
}
else
{
return false;
}
}
return true;
}
/**
* Get a cache handler
* $$$ hugh - added $listModel arg, needed so we can see if they have set "Disable Caching" on the List
*
* @param object $listModel List Model
* @param string $group group name (will default to package)
* @param int $ttl time to live in minutes, defaults to J! config
*
* @since 3.0.7
*
* @return JCache
*/
public static function getCache($listModel = null, $group = null, $ttl = null)
{
$config = JFactory::getConfig();
$app = JFactory::getApplication();
$package = isset($group) ? $group : $app->getUserState('com_fabrik.package', 'fabrik');
$time = isset($ttl) ? (int) $ttl : (int) $config->get('cachetime');
$base = JPATH_BASE . '/cache/';
$opts = array('defaultgroup' => 'com_' . $package, 'cachebase' => $base, 'lifetime' => $time, 'language' => 'en-GB', 'storage' => 'file');
$cache = JCache::getInstance('callback', $opts);
$doCache = $config->get('caching', 0) > 0 ? true : false;
if ($doCache && $listModel !== null)
{
$doCache = $listModel->getParams()->get('list_disable_caching', '0') == '0';
}
$cache->setCaching($doCache);
return $cache;
}
/**
* Is caching enabled
*
* @param object $listModel List Model
* @param bool $noGuest disable caching for guests
*
* @since 3.8
*
* @return JCache
*/
public static function useCache($listModel = null, $noGuest = true, $excludedFormats = null)
{
$config = JFactory::getConfig();
$app = JFactory::getApplication();
if (!isset($excludedFormats))
{
$excludedFormats = array('raw', 'csv', 'pdf', 'json', 'fabrikfeed', 'feed');
}
// check global J! system cache setting
$doCache = $config->get('caching', 0) > 0 ? true : false;
// if enabled, see if any other settingg disables it
if ($doCache)
{
// Check the Fabrik global option
$fabrikConfig = JComponentHelper::getParams('com_fabrik');
if ($fabrikConfig->get('disable_caching', '0') === '1')
{
return false;
}
// If a list model has been specified, see if caching is disabled for this list
if ($listModel !== null)
{
if ($listModel->getParams()->get('list_disable_caching', '0') === '1')
{
return false;
}
}
// Check if caching is disabled for guests
if ($noGuest)
{
if (JFactory::getUser()->get('id') === '0')
{
return false;
}
}
if (in_array($app->input->get('format'), $excludedFormats))
{
return false;
}
}
return true;
}
/**
* Get the default values for a given JForm
*
* @param string $form Form name e.g. list, form etc.
*
* @since 3.0.7
*
* @return array key field name, value default value
*/
public static function formDefaults($form)
{
JForm::addFormPath(JPATH_COMPONENT . '/models/forms');
JForm::addFieldPath(JPATH_COMPONENT . '/models/fields');
$form = JForm::getInstance('com_fabrik.' . $form, $form, array('control' => '', 'load_data' => true));
$fs = $form->getFieldset();
$json = array('params' => array());
foreach ($fs as $name => $field)
{
if (substr($name, 0, 7) === 'params_')
{
$name = str_replace('params_', '', $name);
$json['params'][$name] = $field->value;
}
else
{
$json[$name] = $field->value;
}
}
return $json;
}
/**
* Are we in J3 or using a bootstrap tmpl
*
* @since 3.1
*
* @return bool
*/
public static function j3()
{
$app = JFactory::getApplication();
$version = new JVersion;
// Only use template test for testing in 2.5 with my temp J bootstrap template.
$tpl = $app->getTemplate();
return ($tpl === 'bootstrap' || $tpl === 'fabrik4' || $version->RELEASE > 2.5);
}
/**
* Are we in a form process task
*
* @since 3.2
*
* @return bool
*/
public static function inFormProcess()
{
$app = JFactory::getApplication();
return $app->input->get('task') == 'form.process' || ($app->isAdmin() && $app->input->get('task') == 'process');
}
/**
* Are we in an AJAX validation process task
*
* @since 3.9.2
*
* @return bool
*/
public static function inAJAXValidation()
{
$app = JFactory::getApplication();
return $app->input->get('task', '') === 'form.ajax_validate';
}
/**
* Remove messages from JApplicationCMS
*
* @param CMSApplication $app Application to kill messages from
* @param string $type Message type e.g. 'warning', 'error'
*
* @return array Remaining messages.
*/
public static function killMessage(CMSApplication $app, $type)
{
$appReflection = new \ReflectionClass(get_class($app));
$_messageQueue = $appReflection->getProperty('_messageQueue');
$_messageQueue->setAccessible(true);
$messages = $_messageQueue->getValue($app);
foreach ($messages as $key => $message)
{
if ($message['type'] == $type)
{
unset($messages[$key]);
}
}
$_messageQueue->setValue($app, $messages);
return $messages;
}
/**
* Loose casing to boolean
*
* @param mixed $var Var to test
* @param boolean $default if neither a truish or falsy match are found
*
* @return bool - Set to false if false is found.
*/
public static function toBoolean($var, $default)
{
if ($var === 'false' || $var === 0 || $var === false)
{
return false;
}
if ($var === 'true' || $var === 1 || $var === true)
{
return true;
}
return $default;
}
/**
* Get a getID3 instance - check if library installed, if not, toss an exception
*
* @return object|bool - getid3 object or false if lib not installed
*/
public static function getID3Instance()
{
$getID3 = false;
if (JFile::exists(COM_FABRIK_LIBRARY . '/libs/getid3/getid3/getid3.php'))
{
ini_set('display_errors', true);
require_once COM_FABRIK_LIBRARY . '/libs/getid3/getid3/getid3.php';
require_once COM_FABRIK_LIBRARY . '/libs/getid3/getid3/getid3.lib.php';
\getid3_lib::IncludeDependency(COM_FABRIK_LIBRARY . '/libs/getid3/getid3/extension.cache.mysqli.php', __FILE__, true);
$config = JFactory::getConfig();
$host = $config->get('host');
$database = $config->get('db');
$username = $config->get('user');
$password = $config->get('password');
$getID3 = new \getID3_cached_mysqli($host, $database, $username, $password);
}
return $getID3;
}
public static function getMemoryLimit($symbolic = false)
{
$memory = trim(ini_get('memory_limit'));
$memory = trim($memory);
if ($symbolic)
{
return $memory;
}
$last = strtolower($memory[strlen($memory)-1]);
$val = substr($memory, 0, -1);
switch($last) {
case 'g':
$val *= 1024;
case 'm':
$val *= 1024;
case 'k':
$val *= 1024;
}
return $val;
}
}