Release of v5.0.0-alpha8

Add power path override option on component level. Fix the sql build feature. #1032.
This commit is contained in:
2024-04-06 23:41:34 +02:00
parent 2f64eec95b
commit 2b7b8f90e1
762 changed files with 1900 additions and 1246 deletions

View File

@@ -0,0 +1,283 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace VDM\Joomla\Componentbuilder\Compiler\Customcode;
use VDM\Joomla\Utilities\StringHelper;
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
use VDM\Joomla\Componentbuilder\Compiler\Customcode;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
use VDM\Joomla\Componentbuilder\Compiler\Customcode\Gui;
use VDM\Joomla\Componentbuilder\Compiler\Customcode\Hash;
use VDM\Joomla\Componentbuilder\Compiler\Customcode\LockBase;
use VDM\Joomla\Componentbuilder\Compiler\Interfaces\Customcode\DispenserInterface;
/**
* Compiler Custom Code Dispenser
*
* @since 3.2.0
*/
class Dispenser implements DispenserInterface
{
/**
* Customcode Dispenser Hub
*
* @var array
* @since 3.2.0
**/
public array $hub;
/**
* Compiler Placeholder
*
* @var Placeholder
* @since 3.2.0
**/
protected Placeholder $placeholder;
/**
* Compiler Customcode
*
* @var Customcode
* @since 3.2.0
**/
protected Customcode $customcode;
/**
* Compiler Customcode in Gui
*
* @var Gui
* @since 3.2.0
**/
protected Gui $gui;
/**
* Compiler Customcode to Hash
*
* @var Hash
* @since 3.2.0
**/
protected Hash $hash;
/**
* Compiler Customcode to LockBase
*
* @var LockBase
* @since 3.2.0
**/
protected LockBase $base64;
/**
* Constructor.
*
* @param Placeholder|null $placeholder The compiler placeholder object.
* @param Customcode|null $customcode The compiler customcode object.
* @param Gui|null $gui The compiler customcode gui object.
* @param Hash|null $hash The compiler customcode hash object.
* @param LockBase|null $base64 The compiler customcode lock base64 object.
*
* @since 3.2.0
*/
public function __construct(?Placeholder $placeholder = null, ?Customcode $customcode = null,
?Gui $gui = null, ?Hash $hash = null, ?LockBase $base64 = null)
{
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
$this->customcode = $customcode ?: Compiler::_('Customcode');
$this->gui = $gui ?: Compiler::_('Customcode.Gui');
$this->hash = $hash ?: Compiler::_('Customcode.Hash');
$this->base64 = $base64 ?: Compiler::_('Customcode.LockBase');
}
/**
* Set the script for the customcode dispenser
*
* @param string $script The script
* @param string $first The first key
* @param string|null $second The second key (if not set we use only first key)
* @param string|null $third The third key (if not set we use only first and second key)
* @param array $config The config options
* @param bool $base64 The switch to decode base64 the script
* default: true
* @param bool $dynamic The switch to dynamic update the script
* default: true
* @param bool $add The switch to add to exiting instead of replace
* default: false
*
* @return bool true on success
* @since 3.2.0
*/
public function set(&$script, string $first, ?string $second = null, ?string $third = null,
array $config = [], bool $base64 = true, bool $dynamic = true, bool $add = false): bool
{
// only load if we have a string
if (!StringHelper::check($script))
{
return false;
}
// init all needed arrays
$this->initHub($first, $second, $third, $add);
// prep the script string
if ($base64 && $dynamic)
{
$script = $this->customcode->update(base64_decode($script));
}
elseif ($base64)
{
$script = base64_decode($script);
}
elseif ($dynamic) // this does not happen (just in-case)
{
$script = $this->customcode->update($script);
}
// check if we still have a string
if (StringHelper::check($script))
{
// now load the placeholder snippet if needed
if ($base64 || $dynamic)
{
$script = $this->gui->set($script, $config);
}
// add Dynamic HASHING option of a file/string
$script = $this->hash->set($script);
// add base64 locking option of a string
$script = $this->base64->set($script);
// load the script
$this->setHub($script, $first, $second, $third, $add);
return true;
}
return false;
}
/**
* Get the script from the customcode dispenser
*
* @param string $first The first key
* @param string $second The second key
* @param string $prefix The prefix to add in front of the script if found
* @param string|null $note The switch/note to add to the script
* @param bool $unset The switch to unset the value if found
* @param mixed|null $default The switch/string to use as default return if script not found
* @param string $suffix The suffix to add after the script if found
*
* @return mixed The string/script if found or the default value if not found
* @since 3.2.0
*/
public function get(string $first, string $second, string $prefix = '', ?string $note = null,
bool $unset = false, $default = null, string $suffix = '')
{
// default is to return an empty string
$script = '';
// check if there is any custom script
if (isset($this->hub[$first][$second])
&& StringHelper::check(
$this->hub[$first][$second]
))
{
// add not if set
if ($note)
{
$script .= $note;
}
// load the actual script
$script .= $prefix . $this->placeholder->update_(
(string) $this->hub[$first][$second]
) . $suffix;
// clear some memory
if ($unset)
{
unset($this->hub[$first][$second]);
}
}
// if not found return default
if (!StringHelper::check($script) && $default)
{
return $default;
}
return $script;
}
/**
* Make sure the hub arrays are all set
*
* @param string $first The first key
* @param string|null $second The second key (if not set we use only first key)
* @param string|null $third The third key (if not set we use only first and second key)
* @param bool $add The switch to add to exiting instead of replace
* default: false
*
* @return void
* @since 3.2.0
*/
protected function initHub(string $first, ?string $second = null, ?string $third = null, bool $add = false)
{
if (!isset($this->hub[$first]))
{
$this->hub[$first] = ($second !== null || $add) ? ($second !== null ? [] : '') : [];
}
if ($second !== null && !isset($this->hub[$first][$second]))
{
$this->hub[$first][$second] = ($third !== null || $add) ? ($third !== null ? [] : '') : [];
}
if ($third !== null && !isset($this->hub[$first][$second][$third]))
{
$this->hub[$first][$second][$third] = $add ? '' : [];
}
}
/**
* Set a script in the hub
*
* @param string $script The script
* @param string $first The first key
* @param string|null $second The second key (if not set we use only first key)
* @param string|null $third The third key (if not set we use only first and second key)
* @param bool $add The switch to add to exiting instead of replace
* default: false
*
* @return void
* @since 3.2.0
*/
protected function setHub(string $script, string $first, ?string $second = null, ?string $third = null, bool $add = false)
{
// Load the script
if ($second !== null)
{
if ($third !== null)
{
$this->hub[$first][$second][$third] =
$add ? $this->hub[$first][$second][$third] . $script : $script;
}
else
{
$this->hub[$first][$second] =
$add ? $this->hub[$first][$second] . $script : $script;
}
}
else
{
$this->hub[$first] =
$add ? $this->hub[$first] . $script : $script;
}
}
}

View File

@@ -0,0 +1,392 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace VDM\Joomla\Componentbuilder\Compiler\Customcode;
use Joomla\CMS\Factory;
use Joomla\CMS\User\User;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Filesystem\Path;
use VDM\Joomla\Utilities\ArrayHelper;
use VDM\Joomla\Utilities\StringHelper;
use VDM\Joomla\Utilities\GetHelper;
use VDM\Joomla\Utilities\FileHelper;
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
use VDM\Joomla\Componentbuilder\Compiler\Interfaces\Customcode\ExternalInterface;
/**
* Compiler External Custom Code
*
* @since 3.2.0
*/
class External implements ExternalInterface
{
/**
* The external code/string to be added
*
* @var array
* @since 3.2.0
*/
protected array $code = [];
/**
* The external code/string cutter
*
* @var array
* @since 3.2.0
*/
protected array $cutter = [];
/**
* Compiler Placeholder
*
* @var Placeholder
* @since 3.2.0
**/
protected Placeholder $placeholder;
/**
* Database object to query local DB
*
* @since 3.2.0
**/
protected $db;
/**
* User object
*
* @since 3.2.0
**/
protected $user;
/**
* Database object to query local DB
*
* @since 3.2.0
**/
protected $app;
/**
* Constructor.
*
* @param Placeholder|null $placeholder The compiler placeholder object.
*
* @throws \Exception
* @since 3.2.0
*/
public function __construct(?Placeholder $placeholder = null)
{
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
$this->db = Factory::getDbo();
$this->user = Factory::getUser();
$this->app = Factory::getApplication();
}
/**
* Set the external code string & load it in to string
*
* @param string $string The content to check
* @param int $debug The switch to debug the update
*
* @return string
* @since 3.2.0
*/
public function set(string $string, int $debug = 0): string
{
// check if content has custom code placeholder
if (strpos($string, '[EXTERNA' . 'LCODE=') !== false)
{
// if debug
if ($debug)
{
echo 'External Code String:';
var_dump($string);
}
// target content
$bucket = [];
$found = GetHelper::allBetween(
$string, '[EXTERNA' . 'LCODE=', ']'
);
if (ArrayHelper::check($found))
{
// build local bucket
foreach ($found as $target)
{
// check for cutting sequence
// example: >{3|4
// will cut 3 rows at top and 4 rows at bottom
// if the external code has 8 or more lines
if (($pos = strpos((string) $target, '>{')) !== false)
{
// the length
$target_len = strlen((string) $target);
// where to cut
$cutting = $target_len - $pos;
// get the sequence
$sequence = substr((string) $target, "-$cutting");
// remove from the URL
$target_url = str_replace($sequence, '', (string) $target);
// set the cut key for this target if not set
$this->cutter[trim((string) $target)] = str_replace('>{', '', $sequence);
}
else
{
$target_url = $target;
}
// check if the target is valid URL or path
if ((!filter_var($target_url, FILTER_VALIDATE_URL) === false
&& FileHelper::exists($target_url))
|| (Path::clean($target_url) === $target_url
&& FileHelper::exists($target_url)))
{
$this->getCode($target, $bucket);
}
// give notice that target is not a valid url/path
else
{
// set key
$key = '[EXTERNA' . 'LCODE=' . $target . ']';
// set the notice
$this->app->enqueueMessage(
Text::_('COM_COMPONENTBUILDER_HR_HTHREEEXTERNAL_CODE_WARNINGHTHREE'
), 'Warning'
);
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_THE_BSB_IS_NOT_A_VALID_URLPATH',
$key
), 'Warning'
);
// remove the placeholder
$bucket[$key] = '';
}
}
// now update local string if bucket has values
if (ArrayHelper::check($bucket))
{
$string = $this->placeholder->update($string, $bucket);
}
}
// if debug
if ($debug)
{
echo 'External Code String After Update:';
var_dump($string);
}
}
return $string;
}
/**
* Get the External Code/String
*
* @param string $string The content to check
* @param array $bucket The Placeholders bucket
*
* @return void
* @since 3.2.0
*/
protected function getCode(string $target, array &$bucket)
{
// set URL key
$target_key = trim($target);
// set key
$key = '[EXTERNA' . 'LCODE=' . $target . ']';
// remove the cut sequence from the url
if (isset($this->cutter[$target_key]))
{
// remove from the URL
$target_url = trim(str_replace('>{' . $this->cutter[$target_key], '', $target));
}
else
{
$target_url = trim($target);
}
// check if we already fetched this
if (!isset($this->code[$target_key]))
{
// get the data string (code)
$this->code[$target_key]
= FileHelper::getContent($target_url);
// check if we must cut this
if (isset($this->cutter[$target_key]) &&
$this->cutter[$target_key])
{
$this->code[$target_key] = $this->cut(
$this->code[$target_key],
$this->cutter[$target_key],
$key
);
}
// did we get any value
if (StringHelper::check(
$this->code[$target_key]
))
{
// check for changes
$live_hash = md5($this->code[$target_key]);
// check if it exists local
if ($hash = GetHelper::var(
'external_code', $target_key, 'target', 'hash'
))
{
// must be an admin make a change to use EXTERNAL code (we may add a custom access switch - use ADMIN for now)
if ($hash !== $live_hash && $this->user->authorise(
'core.admin', 'com_componentbuilder'
))
{
// update the hash since it changed
$object = new \stdClass();
$object->target = $target_key;
$object->hash = $live_hash;
// update local hash
$this->db->updateObject(
'#__componentbuilder_external_code', $object,
'target'
);
// give notice of the change
$this->app->enqueueMessage(
Text::_('COM_COMPONENTBUILDER_HR_HTHREEEXTERNAL_CODE_WARNINGHTHREE'),
'Warning'
);
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_THE_CODESTRING_FROM_BSB_HAS_BEEN_BCHANGEDB_SINCE_THE_LAST_COMPILATION_PLEASE_INVESTIGATE_TO_ENSURE_THE_CHANGES_ARE_SAFE_BSHOULD_YOU_NOT_EXPECT_THIS_CHANGE_TO_THE_EXTERNAL_CODESTRING_BEING_ADDED_THEN_THIS_IS_A_SERIOUS_ISSUE_AND_REQUIRES_IMMEDIATE_ATTENTIONB_DO_NOT_IGNORE_THIS_WARNING_AS_IT_WILL_ONLY_SHOW_BONCEB',
$key
), 'Warning'
);
}
elseif ($hash !== $live_hash)
{
// set the notice
$this->app->enqueueMessage(
Text::_('COM_COMPONENTBUILDER_HR_HTHREEEXTERNAL_CODE_ERRORHTHREE'),
'Error'
);
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_S_WE_DETECTED_A_CHANGE_IN_BEXTERNALCODEB_BUT_YOU_DO_NOT_HAVE_PERMISSION_TO_ALLOW_THIS_CHANGE_SO_BSB_WAS_REMOVED_FROM_THE_COMPILATION_PLEASE_CONTACT_YOUR_SYSTEM_ADMINISTRATOR_FOR_MORE_INFOBR_SMALLADMIN_ACCESS_REQUIREDSMALL',
$this->user->get('name'), $key
), 'Error'
);
// remove the code/string
$this->code[$target_key] = '';
}
}
// only an admin can add new EXTERNAL code (we may add a custom access switch - use ADMIN for now)
elseif ($this->user->authorise(
'core.admin', 'com_componentbuilder'
))
{
// add the hash to track changes
$object = new \stdClass();
$object->target = $target_key;
$object->hash = $live_hash;
// insert local hash
$this->db->insertObject(
'#__componentbuilder_external_code', $object
);
// give notice the first time this is added
$this->app->enqueueMessage(
Text::_('COM_COMPONENTBUILDER_HR_HTHREEEXTERNAL_CODE_NOTICEHTHREE'),
'Warning'
);
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_THE_CODESTRING_FROM_BSB_HAS_BEEN_ADDED_FOR_THE_BFIRST_TIMEB_PLEASE_IINVESTIGATEI_TO_ENSURE_THE_CORRECT_CODESTRING_WAS_USED_BSHOULD_YOU_NOT_KNOW_ABOUT_THIS_NEW_EXTERNAL_CODESTRING_BEING_ADDED_THEN_THIS_IS_A_SERIOUS_DANGER_AND_REQUIRES_IMMEDIATE_ATTENTIONB_DO_NOT_IGNORE_THIS_WARNING_AS_IT_WILL_ONLY_SHOW_BONCEB',
$key
), 'Warning'
);
}
else
{
// set the notice
$this->app->enqueueMessage(
Text::_('COM_COMPONENTBUILDER_HR_HTHREEEXTERNAL_CODE_ERRORHTHREE'),
'Error'
);
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_S_WE_DETECTED_BNEW_EXTERNALCODEB_BUT_YOU_DO_NOT_HAVE_PERMISSION_TO_ALLOW_THIS_NEW_CODESTRING_SO_BSB_WAS_REMOVED_FROM_THE_COMPILATION_PLEASE_CONTACT_YOU_SYSTEM_ADMINISTRATOR_FOR_MORE_INFOBR_SMALLADMIN_ACCESS_REQUIREDSMALL',
$this->user->get('name'), $key
), 'Error'
);
// remove the code/string
$this->code[$target_key] = '';
}
}
else
{
// set notice that we could not get a valid string from the target
$this->app->enqueueMessage(
Text::_('COM_COMPONENTBUILDER_HR_HTHREEEXTERNAL_CODE_WARNINGHTHREE'), 'Error'
);
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_THE_BSB_RETURNED_AN_INVALID_STRING', $key
), 'Error'
);
}
}
// add to local bucket
$bucket[$key] = $this->code[$target_key] ?? '';
}
/**
* Cut the External Code/String
*
* @param string $string The content to cut
* @param string $sequence The cutting sequence
* @param string $key The content key
*
* @return string
* @since 3.2.0
*/
protected function cut(string $string, string $sequence, string $key): string
{
// we first break the string up in rows
$rows = (array) explode(PHP_EOL, $string);
// get the cutting sequence
$cutter = (array) explode('|', $sequence);
// we only continue if we have more rows than we have to cut
if (array_sum($cutter) < ArrayHelper::check($rows))
{
// remove the rows at the bottom if needed
if (isset($cutter[1]) && $cutter[1] > 0)
{
array_splice($rows, "-$cutter[1]");
}
// remove the rows at the top if needed
if ($cutter[0] > 0)
{
$rows = array_splice($rows, $cutter[0]);
}
// return the remaining rows
return implode(PHP_EOL, $rows);
}
// we set an error message about too few lines to cut
$this->app->enqueueMessage(
Text::_('COM_COMPONENTBUILDER_HR_HTHREEEXTERNAL_CODE_NOTICEHTHREE'),
'Error'
);
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_THE_BSB_CUT_SEQUENCE_FAILED_ON_THE_RETURNED_EXTERNAL_CODESTRING_AS_MORE_LINES_HAS_TO_BE_CUT_THEN_WAS_FOUND_IN_THE_CODESTRING_WE_HAVE_COMPLETELY_REMOVED_THE_CODE_PLEASE_CHECK_THIS_CODESTRING',
$key
), 'Error'
);
return '';
}
}

View File

@@ -0,0 +1,940 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace VDM\Joomla\Componentbuilder\Compiler\Customcode;
use Joomla\CMS\Factory;
use Joomla\CMS\User\User;
use Joomla\CMS\Filesystem\Folder;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Version;
use VDM\Joomla\Utilities\ArrayHelper;
use VDM\Joomla\Utilities\StringHelper;
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
use VDM\Joomla\Componentbuilder\Compiler\Config;
use VDM\Joomla\Componentbuilder\Compiler\Customcode\Gui;
use VDM\Joomla\Componentbuilder\Compiler\Customcode\Extractor\Paths;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder\Reverse;
use VDM\Joomla\Componentbuilder\Compiler\Component\Placeholder;
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Pathfix;
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Placefix;
use VDM\Joomla\Componentbuilder\Compiler\Interfaces\Customcode\ExtractorInterface;
/**
* Compiler Custom Code Extractor
*
* The custom script placeholders - we use the (xxx) to avoid detection it should be (***)
* ##################################---> PHP/JS ---####################################
*
* New Insert Code = /xxx[INSERT>$$$$]xxx/ /xxx[/INSERT>$$$$]xxx/
* New Replace Code = /xxx[REPLACE>$$$$]xxx/ /xxx[/REPLACE>$$$$]xxx/
*
* //////////////////////////////// when JCB adds it back //////////////////////////////////
* JCB Add Inserted Code = /xxx[INSERTED$$$$]xxx//xx23xx/ /xxx[/INSERTED$$$$]xxx/
* JCB Add Replaced Code = /xxx[REPLACED$$$$]xxx//xx25xx/ /xxx[/REPLACED$$$$]xxx/
*
* /////////////////////////////// changeing existing custom code /////////////////////////
* Update Inserted Code = /xxx[INSERTED>$$$$]xxx//xx23xx/ /xxx[/INSERTED>$$$$]xxx/
* Update Replaced Code = /xxx[REPLACED>$$$$]xxx//xx25xx/ /xxx[/REPLACED>$$$$]xxx/
*
* The custom script placeholders - we use the (==) to avoid detection it should be (--)
* ###################################---> HTML ---#####################################
*
* New Insert Code = !==[INSERT>$$$$]==> !==[/INSERT>$$$$]==>
* New Replace Code = !==[REPLACE>$$$$]==> !==[/REPLACE>$$$$]==>
*
* ///////////////////////////////// when JCB adds it back ///////////////////////////////
* JCB Add Inserted Code =
* JCB Add Replaced Code =
*
* //////////////////////////// changeing existing custom code ///////////////////////////
* Update Inserted Code = !==[INSERTED>$$$$]==> !==[/INSERTED>$$$$]==>
* Update Replaced Code = !==[REPLACED>$$$$]==> !==[/REPLACED>$$$$]==>
*
* ////////23 is the ID of the code in the system don't change it!!!!!!!!!!!!!!!!!!!!!!!!!!
*
* More info read: https://git.vdm.dev/joomla/Component-Builder/wiki/TIPS:-Custom-Code
*
* @since 3.2.0
*/
class Extractor implements ExtractorInterface
{
/**
* The placeholder keys
*
* @var array
* @since 3.2.0
*/
protected array $PKeys
= [
1 => 'REPLACE<>$$$$]',
2 => 'INSERT<>$$$$]',
3 => 'REPLACED<>$$$$]',
4 => 'INSERTED<>$$$$]'
];
/**
* Current Joomla Version We are IN
*
* @var int
* @since 3.2.0
**/
protected int $currentVersion;
/**
* The custom code in local files that already exist in system
*
* @var array
* @since 3.2.0
*/
protected array $existing = [];
/**
* The custom code in local files that are new
*
* @var array
* @since 3.2.0
*/
protected array $new = [];
/**
* The index of code already loaded
*
* @var array
* @since 3.2.0
*/
protected array $done = [];
/**
* The search counter
*
* @var array
* @since 3.2.0
*/
protected array $counter = [1 => 0, 2 => 0];
/**
* The file types to search
*
* @var array
* @since 3.2.0
*/
protected array $fileTypes = ['\.php', '\.js', '\.xml'];
/**
* The local placeholders
*
* @var array
* @since 3.2.0
*/
protected array $placeholders;
/**
* Today's date in SQL format
*
* @var string
* @since 3.2.0
*/
protected string $today;
/**
* Compiler Config
*
* @var Config
* @since 3.2.0
**/
protected Config $config;
/**
* Compiler Customcode Gui
*
* @var Gui
* @since 3.2.0
**/
protected Gui $gui;
/**
* Compiler Customcode Extractor Paths
*
* @var Paths
* @since 3.2.0
**/
protected Paths $paths;
/**
* Compiler Placeholder Reverse
*
* @var Reverse
* @since 3.2.0
**/
protected Reverse $reverse;
/**
* Compiler Component Placeholder
*
* @var Placeholder
* @since 3.2.0
**/
protected Placeholder $componentPlaceholder;
/**
* Compiler Component Pathfix
*
* @var Pathfix
* @since 3.2.0
**/
protected Pathfix $pathfix;
/**
* Current User Object
*
* @since 3.2.0
**/
protected $user;
/**
* Database object to query local DB
*
* @since 3.2.0
**/
protected $db;
/**
* Database object to query local DB
*
* @since 3.2.0
**/
protected $app;
/**
* Constructor.
*
* @param Config|null $config The compiler config object.
* @param Gui|null $gui The compiler customcode gui object.
* @param Paths|null $paths The compiler customcode extractor paths object.
* @param Reverse|null $reverse The compiler placeholder reverse object.
* @param Placeholder|null $placeholder The compiler component placeholder object.
* @param Pathfix|null $pathfix The compiler path fixing object.
*
* @throws \Exception
* @since 3.2.0
*/
public function __construct(?Config $config = null, ?Gui $gui = null, ?Paths $paths = null,
?Reverse $reverse = null, ?Placeholder $placeholder = null, ?Pathfix $pathfix = null)
{
$this->config = $config ?: Compiler::_('Config');
$this->gui = $gui ?: Compiler::_('Customcode.Gui');
$this->paths = $paths ?: Compiler::_('Customcode.Extractor.Paths');
$this->reverse = $reverse ?: Compiler::_('Placeholder.Reverse');
$this->componentPlaceholder = $placeholder ?: Compiler::_('Component.Placeholder');
$this->pathfix = $pathfix ?: Compiler::_('Utilities.Pathfix');
$this->user = Factory::getUser();
$this->db = Factory::getDbo();
$this->app = Factory::getApplication();
// set today's date
$this->today = Factory::getDate()->toSql();
// set some local placeholders
$placeholders = array_flip(
$this->componentPlaceholder->get()
);
$placeholders[StringHelper::safe(
$this->config->component_code_name, 'F'
) . 'Helper::'] = Placefix::_('Component') . 'Helper::';
$placeholders['COM_' . StringHelper::safe(
$this->config->component_code_name, 'U'
)] = 'COM_' . Placefix::_('COMPONENT');
$placeholders['com_' . $this->config->component_code_name] = 'com_' . Placefix::_('component');
// set the local placeholders
$this->placeholders = array_reverse($placeholders, true);
// set the current version
$this->currentVersion = (int) Version::MAJOR_VERSION;
}
/**
* get the custom code from the local files
*
* @return void
* @since 3.2.0
*/
public function run()
{
// we must first store the current working directory
$joomla = getcwd();
foreach ($this->paths->active as $target => $path)
{
// we are changing the working directory to the component path
chdir($path);
foreach ($this->fileTypes as $type)
{
// get a list of files in the current directory tree (only PHP, JS and XML for now)
$files = Folder::files('.', $type, true, true);
// check if files found
if (ArrayHelper::check($files))
{
foreach ($files as $file)
{
// search the file
$this->searchFileContent($file, $target);
// insert new code
$this->insert(100);
// update existing custom code
$this->update(30);
}
}
}
}
// change back to Joomla working directory
chdir($joomla);
// make sure all code is stored
$this->insert();
// update existing custom code
$this->update();
}
/**
* search a file for placeholders and store result
*
* @param string $file The file path to search
*
* @return array on success
* @since 3.2.0
*/
protected function searchFileContent(&$file, &$target)
{
// we add a new search for the GUI CODE Blocks
$this->gui->search($file, $this->placeholders, $this->today, $target);
// reset each time per file
$loadEndFingerPrint = false;
$endFingerPrint = [];
$fingerPrint = [];
$codeBucket = [];
$pointer = [];
$reading = [];
$reader = 0;
// reset found Start type
$commentType = 0;
// make sure we have the path correct (the script file is not in admin path for example)
// there may be more... will nead to keep our eye on this... since files could be moved during install
$file = str_replace('./', '', (string) $file); # TODO (windows path issues)
$path = $file !== 'script.php' ? $target . '/' . $file : $file;
// now we go line by line
foreach (new \SplFileObject($file) as $lineNumber => $lineContent)
{
// we must keep last few lines to dynamic find target entry later
$fingerPrint[$lineNumber] = trim($lineContent);
// load the end fingerprint
if ($loadEndFingerPrint)
{
$endFingerPrint[$lineNumber] = trim($lineContent);
}
foreach ($this->PKeys as $type => $search)
{
$i = (int) ($type == 3 || $type == 4) ? 2 : 1;
$_type = (int) ($type == 1 || $type == 3) ? 1 : 2;
if ($reader === 0 || $reader === $i)
{
$targetKey = $type;
$start = '/***[' . $search . '***/';
$end = '/***[/' . $search . '***/';
$startHTML = '<!--[' . $search . '-->';
$endHTML = '<!--[/' . $search . '-->';
// check if the ending placeholder was found
if (isset($reading[$targetKey]) && $reading[$targetKey]
&& ((trim((string) $lineContent) === $end
|| strpos((string) $lineContent, $end) !== false)
|| (trim((string) $lineContent) === $endHTML
|| strpos((string) $lineContent, $endHTML) !== false)))
{
// trim the placeholder and if there is still data then load it
if (isset($endReplace)
&& ($_line = $this->addLineChecker($endReplace, 2, $lineContent)) !== false)
{
$codeBucket[$pointer[$targetKey]][] = $_line;
}
// deactivate the reader
$reading[$targetKey] = false;
if ($_type == 2)
{
// deactivate search
$reader = 0;
}
else
{
// activate fingerPrint for replacement end target
$loadEndFingerPrint = true;
$backupTargetKey = $targetKey;
$backupI = $i;
}
// all new records we can do a bulk insert
if ($i === 1)
{
// end the bucket info for this code block
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
(int) $lineNumber
); // 'toline'
// first reverse engineer this code block
$c0de = $this->reverse->engine(
implode('', $codeBucket[$pointer[$targetKey]]),
$this->placeholders, $target
);
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
base64_encode((string) $c0de)
); // 'code'
if ($_type == 2)
{
// load the last value
$this->new[$pointer[$targetKey]][]
= $this->db->quote(0); // 'hashendtarget'
}
}
// the record already exist so we must update instead
elseif ($i === 2)
{
// end the bucket info for this code block
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('to_line') . ' = '
. $this->db->quote($lineNumber);
// first reverse engineer this code block
$c0de = $this->reverse->engine(
implode('', $codeBucket[$pointer[$targetKey]]),
$this->placeholders, $target,
$this->existing[$pointer[$targetKey]]['id']
);
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('code') . ' = '
. $this->db->quote(base64_encode((string) $c0de));
if ($_type == 2)
{
// load the last value
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('hashendtarget')
. ' = ' . $this->db->quote(0);
}
}
}
// check if the endfingerprint is ready to save
if (count((array) $endFingerPrint) === 3)
{
$hashendtarget = '3__' . md5(
implode('', $endFingerPrint)
);
// all new records we can do a bulk insert
if ($i === 1)
{
// load the last value
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
$hashendtarget
); // 'hashendtarget'
}
// the record already exist so we must update
elseif ($i === 2)
{
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('hashendtarget') . ' = '
. $this->db->quote($hashendtarget);
}
// reset the needed values
$endFingerPrint = [];
$loadEndFingerPrint = false;
// deactivate reader (to allow other search)
$reader = 0;
}
// then read in the code
if (isset($reading[$targetKey]) && $reading[$targetKey])
{
$codeBucket[$pointer[$targetKey]][] = $lineContent;
}
// see if the custom code line starts now with PHP/JS comment type
if ((!isset($reading[$targetKey]) || !$reading[$targetKey])
&& (($i === 1 && trim((string) $lineContent) === $start)
|| strpos((string) $lineContent, $start) !== false))
{
$commentType = 1; // PHP/JS type
$startReplace = $start;
$endReplace = $end;
}
// see if the custom code line starts now with HTML comment type
elseif ((!isset($reading[$targetKey])
|| !$reading[$targetKey])
&& (($i === 1 && trim((string) $lineContent) === $startHTML)
|| strpos((string) $lineContent, $startHTML) !== false))
{
$commentType = 2; // HTML type
$startReplace = $startHTML;
$endReplace = $endHTML;
}
// check if the starting place holder was found
if ($commentType > 0)
{
// if we have all on one line we have a problem (don't load it TODO)
if (strpos((string) $lineContent, (string) $endReplace) !== false)
{
// reset found comment type
$commentType = 0;
$this->app->enqueueMessage(
Text::_('COM_COMPONENTBUILDER_HR_HTHREECUSTOM_CODES_WARNINGHTHREE'),
'Warning'
);
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_WE_FOUND_DYNAMIC_CODE_BALL_IN_ONE_LINEB_AND_IGNORED_IT_PLEASE_REVIEW_S_FOR_MORE_DETAILS',
$path
), 'Warning'
);
continue;
}
// do a quick check to insure we have an id
$id = false;
if ($i === 2)
{
$id = $this->getSystemID(
$lineContent,
array(1 => $start, 2 => $startHTML),
$commentType
);
}
if ($i === 2 && $id > 0)
{
// make sure we update it only once even if found again.
if (isset($this->done[$id]))
{
// reset found comment type
$commentType = 0;
continue;
}
// store the id to avoid duplication
$this->done[$id] = (int) $id;
}
// start replace
$startReplace = $this->setStartReplace(
$id, $commentType, $startReplace
);
// set active reader (to lock out other search)
$reader = $i;
// set pointer
$pointer[$targetKey] = $this->counter[$i];
// activate the reader
$reading[$targetKey] = true;
// start code bucket
$codeBucket[$pointer[$targetKey]] = [];
// trim the placeholder and if there is still data then load it
if ($_line = $this->addLineChecker(
$startReplace, 1, $lineContent
))
{
$codeBucket[$pointer[$targetKey]][] = $_line;
}
// get the finger print around the custom code
$inFinger = count($fingerPrint);
$getFinger = $inFinger - 1;
$hasharray = array_slice(
$fingerPrint, -$inFinger, $getFinger, true
);
$hasleng = count($hasharray);
$hashtarget = $hasleng . '__' . md5(
implode('', $hasharray)
);
// for good practice
$this->pathfix->set($path);
// all new records we can do a bulk insert
if ($i === 1 || !$id)
{
// start the bucket for this code
$this->new[$pointer[$targetKey]] = [];
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
$path
); // 'path'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
(int) $_type
); // 'type'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
1
); // 'target'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
$this->currentVersion
); // 'joomla_version'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
$commentType
); // 'comment_type'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
(int) $this->config->component_id
); // 'component'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
1
); // 'published'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
$this->today
); // 'created'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
(int) $this->user->id
); // 'created_by'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
1
); // 'version'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
1
); // 'access'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
$hashtarget
); // 'hashtarget'
$this->new[$pointer[$targetKey]][]
= $this->db->quote(
(int) $lineNumber
); // 'fromline'
}
// the record already exist so we must update instead
elseif ($i === 2 && $id > 0)
{
// start the bucket for this code
$this->existing[$pointer[$targetKey]] = [];
$this->existing[$pointer[$targetKey]]['id']
= (int) $id;
$this->existing[$pointer[$targetKey]]['conditions'] = [];
$this->existing[$pointer[$targetKey]]['conditions'][]
= $this->db->quoteName('id') . ' = '
. $this->db->quote($id);
$this->existing[$pointer[$targetKey]]['fields'] = [];
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('path') . ' = '
. $this->db->quote($path);
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('type') . ' = '
. $this->db->quote($_type);
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('comment_type') . ' = '
. $this->db->quote($commentType);
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('component') . ' = '
. $this->db->quote($this->config->component_id);
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('from_line') . ' = '
. $this->db->quote($lineNumber);
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('modified') . ' = '
. $this->db->quote($this->today);
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('modified_by') . ' = '
. $this->db->quote($this->user->id);
$this->existing[$pointer[$targetKey]]['fields'][]
= $this->db->quoteName('hashtarget') . ' = '
. $this->db->quote($hashtarget);
}
else // this should actualy never happen
{
// de activate the reader
$reading[$targetKey] = false;
$reader = 0;
}
// reset found comment type
$commentType = 0;
// update the counter
$this->counter[$i]++;
}
}
}
// make sure only a few lines is kept at a time
if (count((array) $fingerPrint) > 10)
{
$fingerPrint = array_slice($fingerPrint, -6, 6, true);
}
}
// if the code is at the end of the page and there were not three more lines
if (count((array) $endFingerPrint) > 0 || $loadEndFingerPrint)
{
if (count((array) $endFingerPrint) > 0)
{
$leng = count($endFingerPrint);
$hashendtarget = $leng . '__' . md5(
implode('', $endFingerPrint)
);
}
else
{
$hashendtarget = 0;
}
// all new records we can do a buldk insert
if ($backupI === 1)
{
// load the last value
$this->new[$pointer[$backupTargetKey]][]
= $this->db->quote($hashendtarget); // 'hashendtarget'
}
// the record already exist so we must use module to update
elseif ($backupI === 2)
{
$this->existing[$pointer[$backupTargetKey]]['fields'][]
= $this->db->quoteName('hashendtarget') . ' = '
. $this->db->quote($hashendtarget);
}
}
}
/**
* Insert the code
*
* @param int $when To set when to update
*
* @return void
* @since 3.2.0
*/
protected function insert(int $when = 1)
{
if (ArrayHelper::check($this->new) >= $when)
{
// Create a new query object.
$query = $this->db->getQuery(true);
$continue = false;
// Insert columns.
$columns = array('path', 'type', 'target', 'joomla_version', 'comment_type',
'component', 'published', 'created', 'created_by',
'version', 'access', 'hashtarget', 'from_line',
'to_line', 'code', 'hashendtarget');
// Prepare the insert query.
$query->insert(
$this->db->quoteName('#__componentbuilder_custom_code')
);
$query->columns($this->db->quoteName($columns));
foreach ($this->new as $values)
{
if (count((array) $values) == 15)
{
$query->values(implode(',', $values));
$continue = true;
}
else
{
// TODO line mismatch... should not happen
}
}
// clear the values array
$this->new = [];
if (!$continue)
{
return; // insure we don't continue if no values were loaded
}
// Set the query using our newly populated query object and execute it.
$this->db->setQuery($query);
$this->db->execute();
}
}
/**
* Update the code
*
* @param int $when To set when to update
*
* @return void
* @since 3.2.0
*/
protected function update(int $when = 1)
{
if (ArrayHelper::check($this->existing) >= $when)
{
foreach ($this->existing as $code)
{
// Create a new query object.
$query = $this->db->getQuery(true);
// Prepare the update query.
$query->update(
$this->db->quoteName('#__componentbuilder_custom_code')
)->set($code['fields'])->where($code['conditions']);
// Set the query using our newly populated query object and execute it.
$this->db->setQuery($query);
$this->db->execute();
}
// clear the values array
$this->existing = [];
}
}
/**
* set the start replace placeholder
*
* @param int $id The comment id
* @param int $commentType The comment type
* @param string $startReplace The main replace string
*
* @return string on success
* @since 3.2.0
*/
protected function setStartReplace(int $id, int $commentType, string $startReplace): string
{
if ($id > 0)
{
switch ($commentType)
{
case 1: // the PHP & JS type
$startReplace .= '/*' . $id . '*/';
break;
case 2: // the HTML type
$startReplace .= '<!--' . $id . '-->';
break;
}
}
return $startReplace;
}
/**
* Check if this line should be added
*
* @param string $replaceKey The key to remove from line
* @param int $type The line type
* @param string $lineContent The line to check
*
* @return bool|int true on success
* @since 3.2.0
*/
protected function addLineChecker(string $replaceKey, int $type, string $lineContent)
{
$check = explode($replaceKey, $lineContent);
switch ($type)
{
case 1:
// beginning of code
if (isset($check[1]) && StringHelper::check($check[1]))
{
return trim($check[1]);
}
break;
case 2:
// end of code
if (isset($check[0]) && StringHelper::check($check[0]))
{
return trim($check[0]);
}
break;
}
return false;
}
/**
* search for the system id in the line given
*
* @param string $lineContent The file path to search
* @param array $placeholders The values to search for
* @param int $commentType The comment type
*
* @return mixed on success
* @since 3.2.0
*/
protected function getSystemID(string &$lineContent, array $placeholders, int $commentType)
{
$trim = '/';
if ($commentType == 2)
{
$trim = '<!--';
}
// remove place holder from content
$string = trim(
str_replace($placeholders[$commentType] . $trim, '', $lineContent)
);
// now get all numbers
$numbers = [];
preg_match_all('!\d+!', $string, $numbers);
// return the first number
if (isset($numbers[0])
&& ArrayHelper::check(
$numbers[0]
))
{
return reset($numbers[0]);
}
return false;
}
}

View File

@@ -0,0 +1,418 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace VDM\Joomla\Componentbuilder\Compiler\Customcode\Extractor;
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\Folder;
use VDM\Joomla\Utilities\ArrayHelper;
use VDM\Joomla\Utilities\JsonHelper;
use VDM\Joomla\Utilities\GetHelper;
use VDM\Joomla\Utilities\String\ClassfunctionHelper;
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
use VDM\Joomla\Componentbuilder\Compiler\Config;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
use VDM\Joomla\Componentbuilder\Compiler\Component\Placeholder as ComponentPlaceholder;
use VDM\Joomla\Componentbuilder\Compiler\Customcode;
use VDM\Joomla\Componentbuilder\Compiler\Language\Extractor;
/**
* Compiler Custom Code Paths
*
* @since 3.2.0
*/
class Paths
{
/**
* The local paths
*
* @var array
* @since 3.2.0
**/
public array $active = [];
/**
* Compiler Component Placeholder
*
* @var array
* @since 3.2.0
**/
protected array $componentPlaceholder;
/**
* Compiler Config
*
* @var Config
* @since 3.2.0
**/
protected Config $config;
/**
* Compiler Placeholder
*
* @var Placeholder
* @since 3.2.0
**/
protected Placeholder $placeholder;
/**
* Compiler Customcode
*
* @var Customcode
* @since 3.2.0
**/
protected Customcode $customcode;
/**
* Compiler Language Extractor
*
* @var Extractor
* @since 3.2.0
**/
protected Extractor $extractor;
/**
* Database object to query local DB
*
* @since 3.2.0
**/
protected $db;
/**
* Constructor.
*
* @param Config|null $config The compiler config object.
* @param Placeholder|null $placeholder The compiler placeholder object.
* @param ComponentPlaceholder|null $componentPlaceholder The compiler component placeholder object.
* @param Customcode|null $customcode The compiler customcode object.
* @param Extractor|null $extractor The compiler language extractor object.
*
* @throws \Exception
* @since 3.2.0
*/
public function __construct(?Config $config = null, ?Placeholder $placeholder = null,
?ComponentPlaceholder $componentPlaceholder = null, ?Customcode $customcode = null,
?Extractor $extractor = null)
{
$this->config = $config ?: Compiler::_('Config');
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
/** @var ComponentPlaceholder $componentPlaceholder */
$componentPlaceholder = $componentPlaceholder ?: Compiler::_('Component.Placeholder');
$this->customcode = $customcode ?: Compiler::_('Customcode');
$this->extractor = $extractor ?: Compiler::_('Language.Extractor');
$this->db = Factory::getDbo();
// load the placeholders to local array
$this->componentPlaceholder = $componentPlaceholder->get();
// load the paths on initialization
$this->load();
}
/**
* get the local installed path of this component
*
* @return void
* @since 3.2.0
*/
protected function load()
{
// set the local paths to search
$local_paths = [];
// admin path
$local_paths['admin'] = JPATH_ADMINISTRATOR . '/components/com_'
. $this->config->component_code_name;
// site path
$local_paths['site'] = JPATH_ROOT . '/components/com_'
. $this->config->component_code_name;
// media path
$local_paths['media'] = JPATH_ROOT . '/media/com_'
. $this->config->component_code_name;
// power path
$local_paths['power'] = JPATH_ROOT . '/' . $this->config->get('jcb_powers_path', 'libraries/jcb_powers');
// lets also go over the REPOS - TODO
// Painful but we need to folder paths for the linked modules
if (($module_ids = $this->getModuleIDs()) !== false)
{
foreach ($module_ids as $module_id)
{
// get the module folder path
if (($path = $this->getModulePath($module_id)) !== false)
{
// set the path
$local_paths['module_' . str_replace('/', '_', (string) $path)] = $path;
}
}
}
// Painful but we need to folder paths for the linked plugins
if (($plugin_ids = $this->getPluginIDs()) !== false)
{
foreach ($plugin_ids as $plugin_id)
{
// get the plugin group and folder name
if (($path = $this->getPluginPath($plugin_id)) !== false)
{
// set the path
$local_paths['plugin_' . str_replace('/', '_', (string) $path)] = JPATH_ROOT . '/plugins/' . $path;
}
}
}
// check if the local install is found
foreach ($local_paths as $key => $localPath)
{
if (!Folder::exists($localPath))
{
unset($local_paths[$key]);
}
}
if (ArrayHelper::check($local_paths))
{
$this->active = $local_paths;
}
}
/**
* get the Joomla Modules IDs
*
* @return mixed of IDs on success
* @since 3.2.0
*/
protected function getModuleIDs()
{
if (($addjoomla_modules = GetHelper::var(
'component_modules', $this->config->component_id, 'joomla_component',
'addjoomla_modules'
)) !== false)
{
$addjoomla_modules = (JsonHelper::check(
$addjoomla_modules
)) ? json_decode((string) $addjoomla_modules, true) : null;
if (ArrayHelper::check($addjoomla_modules))
{
$joomla_modules = array_filter(
array_values($addjoomla_modules),
// only load the modules whose target association call for it
fn($array): bool => !isset($array['target']) || $array['target'] != 2
);
// if we have values we return IDs
if (ArrayHelper::check($joomla_modules))
{
return array_map(
fn($array) => (int) $array['module'], $joomla_modules
);
}
}
}
return false;
}
/**
* get the Joomla module path
*
* @return mixed of module path and target site area on success
* @since 3.2.0
*/
protected function getModulePath($id)
{
if (is_numeric($id) && $id > 0)
{
// Create a new query object.
$query = $this->db->getQuery(true);
$query->select('a.*');
$query->select(
$this->db->quoteName(
array(
'a.name',
'a.target'
), array(
'name',
'target'
)
)
);
// from these tables
$query->from('#__componentbuilder_joomla_module AS a');
$query->where($this->db->quoteName('a.id') . ' = ' . (int) $id);
$this->db->setQuery($query);
$this->db->execute();
if ($this->db->getNumRows())
{
// get the module data
$module = $this->db->loadObject();
// update the name if it has dynamic values
$module->name = $this->placeholder->update(
$this->customcode->update($module->name),
$this->componentPlaceholder
);
// set safe class function name
$module->code_name
= ClassfunctionHelper::safe(
$module->name
);
// set module folder name
$module->folder_name = 'mod_' . strtolower((string) $module->code_name);
// set the lang key
$this->extractor->langKeys[strtoupper($module->folder_name)] =
$module->id . '_M0dUl3';
// return the path
if ($module->target == 2)
{
// administrator client area
return JPATH_ADMINISTRATOR . '/modules/'
. $module->folder_name;
}
else
{
// default is the site client area
return JPATH_ROOT . '/modules/' . $module->folder_name;
}
}
}
return false;
}
/**
* get the Joomla plugins IDs
*
* @return mixed of IDs on success
* @since 3.2.0
*/
protected function getPluginIDs()
{
if (($addjoomla_plugins = GetHelper::var(
'component_plugins', $this->config->component_id, 'joomla_component',
'addjoomla_plugins'
)) !== false)
{
$addjoomla_plugins = (JsonHelper::check(
$addjoomla_plugins
)) ? json_decode((string) $addjoomla_plugins, true) : null;
if (ArrayHelper::check($addjoomla_plugins))
{
$joomla_plugins = array_filter(
array_values($addjoomla_plugins),
function ($array) {
// only load the plugins whose target association call for it
if (!isset($array['target']) || $array['target'] != 2)
{
return true;
}
return false;
}
);
// if we have values we return IDs
if (ArrayHelper::check($joomla_plugins))
{
return array_map(
fn($array) => (int) $array['plugin'], $joomla_plugins
);
}
}
}
return false;
}
/**
* get the Joomla plugin path
*
* @return mixed of plugin path on success
* @deprecated 3.3
*/
protected function getPluginPath($id)
{
if (is_numeric($id) && $id > 0)
{
// Create a new query object.
$query = $this->db->getQuery(true);
$query->select('a.*');
$query->select(
$this->db->quoteName(
array(
'a.name',
'g.name'
), array(
'name',
'group'
)
)
);
// from these tables
$query->from('#__componentbuilder_joomla_plugin AS a');
$query->join(
'LEFT', $this->db->quoteName(
'#__componentbuilder_joomla_plugin_group', 'g'
) . ' ON (' . $this->db->quoteName('a.joomla_plugin_group')
. ' = ' . $this->db->quoteName('g.id') . ')'
);
$query->where($this->db->quoteName('a.id') . ' = ' . (int) $id);
$this->db->setQuery($query);
$this->db->execute();
if ($this->db->getNumRows())
{
// get the plugin data
$plugin = $this->db->loadObject();
// update the name if it has dynamic values
$plugin->name = $this->placeholder->update(
$this->customcode->update($plugin->name),
$this->componentPlaceholder
);
// update the name if it has dynamic values
$plugin->code_name
= ClassfunctionHelper::safe(
$plugin->name
);
// set plugin folder name
$plugin->group = strtolower((string) $plugin->group);
// set plugin file name
$plugin->file_name = strtolower((string) $plugin->code_name);
// set the lang key
$this->extractor->langKeys['PLG_' . strtoupper(
$plugin->group . '_' . $plugin->file_name
)] = $plugin->id . '_pLuG!n';
// return the path
return $plugin->group . '/' . $plugin->file_name;
}
}
return false;
}
}

View File

@@ -0,0 +1 @@
<html><body bgcolor="#FFFFFF"></body></html>

View File

@@ -0,0 +1,261 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace VDM\Joomla\Componentbuilder\Compiler\Customcode;
use Joomla\CMS\Factory;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Language\Text;
use VDM\Joomla\Utilities\ArrayHelper;
use VDM\Joomla\Utilities\StringHelper;
use VDM\Joomla\Utilities\GetHelper;
use VDM\Joomla\Utilities\FileHelper;
use VDM\Joomla\Utilities\String\FieldHelper;
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
use VDM\Joomla\Componentbuilder\Compiler\Config;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder\Reverse;
use VDM\Joomla\Componentbuilder\Compiler\Power\Parser;
use VDM\Joomla\Componentbuilder\Compiler\Interfaces\Customcode\GuiInterface;
/**
* Compiler Gui Custom Code
*
* @since 3.2.0
*/
class Gui implements GuiInterface
{
/**
* Compiler Config
*
* @var Config
* @since 3.2.0
**/
protected Config $config;
/**
* Compiler Placeholder Reverse
*
* @var Reverse
* @since 3.2.0
**/
protected Reverse $reverse;
/**
* Compiler Powers Parser
*
* @var Parser
* @since 3.2.0
**/
protected Parser $parser;
/**
* Database object to query local DB
*
* @since 3.2.0
**/
protected $db;
/**
* Database object to query local DB
*
* @since 3.2.0
**/
protected $app;
/**
* Constructor.
*
* @param Config|null $config The compiler config object.
* @param Reverse|null $reverse The compiler placeholder reverse object.
* @param Parser|null $parser The powers parser object.
*
* @throws \Exception
* @since 3.2.0
*/
public function __construct(?Config $config = null, ?Reverse $reverse = null, ?Parser $parser = null)
{
$this->config = $config ?: Compiler::_('Config');
$this->reverse = $reverse ?: Compiler::_('Placeholder.Reverse');
$this->parser = $parser ?: Compiler::_('Power.Parser');
$this->db = Factory::getDbo();
$this->app = Factory::getApplication();
}
/**
* Set the JCB GUI code placeholder
*
* @param string $string The code string
* @param array $config The placeholder config values
*
* @return string
* @since 3.2.0
*/
public function set(string $string, array $config): string
{
if (StringHelper::check($string))
{
if ($this->config->get('add_placeholders', false)
&& $this->check($string) && ArrayHelper::check($config)
&& isset($config['table']) && StringHelper::check($config['table'])
&& isset($config['field']) && StringHelper::check($config['field'])
&& isset($config['type']) && StringHelper::check($config['type'])
&& isset($config['id']) && is_numeric($config['id']))
{
// if we have a key we must get the ID
if (isset($config['key']) && StringHelper::check($config['key']) && $config['key'] !== 'id')
{
if (($id = GetHelper::var($config['table'], $config['id'], $config['key'], 'id')) !== false && is_numeric($id))
{
$config['id'] = $id;
}
else
{
// we must give a error message to inform the user of this issue. (should never happen)
$this->app->enqueueMessage(
Text::sprintf('COM_COMPONENTBUILDER_ID_MISMATCH_WAS_DETECTED_WITH_THE_SSSS_GUI_CODE_FIELD_SO_THE_PLACEHOLDER_WAS_NOT_SET',
$config['table'], $config['field'],
$config['key'], $config['id']
), 'Error'
);
// check some config
if (!isset($config['prefix']))
{
$config['prefix'] = '';
}
return $config['prefix'] . $string;
}
}
// check some config
if (!isset($config['prefix']))
{
$config['prefix'] = PHP_EOL;
}
// add placeholder based on type of code
switch (strtolower((string) $config['type']))
{
// adding with html commenting
case 'html':
$front = $config['prefix'] . '<!--' . '[JCBGUI.';
$sufix = '$$$$]-->' . PHP_EOL;
$back = '<!--[/JCBGUI' . $sufix;
break;
// adding with php commenting
default:
$front = $config['prefix'] . '/***' . '[JCBGUI.';
$sufix = '$$$$]***/' . PHP_EOL;
$back = '/***[/JCBGUI' . $sufix;
break;
}
return $front . $config['table'] . '.' . $config['field'] . '.'
. $config['id'] . '.' . $sufix . $string . $back;
}
// check some config
if (!isset($config['prefix']))
{
$config['prefix'] = '';
}
return $config['prefix'] . $string;
}
return $string;
}
/**
* search a file for gui code blocks that were updated in the IDE
*
* @param string $file The file path to search
* @param array $placeholders The values to replace in the code being stored
* @param string $today The date for today
* @param string $target The target path type
*
* @return void
* @since 3.2.0
*/
public function search(string &$file, array &$placeholders, string &$today, string &$target)
{
// get file content
$file_content = FileHelper::getContent($file);
// get the USE statements (to reverse engineer super power keys)
$use_statements = $this->parser->getUseStatements($file_content);
$guiCode = [];
// we add a new search for the GUI CODE Blocks
$guiCode[] = GetHelper::allBetween(
$file_content, '/***[JCB' . 'GUI<>', '/***[/JCBGUI' . '$$$$]***/'
);
$guiCode[] = GetHelper::allBetween(
$file_content, '<!--[JCB' . 'GUI<>', '<!--[/JCBGUI' . '$$$$]-->'
);
if (($guiCode = ArrayHelper::merge($guiCode)) !== false
&& ArrayHelper::check($guiCode, true))
{
foreach ($guiCode as $code)
{
$first_line = strtok($code, PHP_EOL);
// get the GUI target details
$query = explode('.', trim($first_line, '.'));
// only continue if we have 3 values in the query
if (is_array($query) && count($query) >= 3)
{
// cleanup the newlines around the code
$code = trim(str_replace($first_line, '', (string) $code), PHP_EOL)
. PHP_EOL;
// set the ID
$id = (int) $query[2];
// make the field name save
$field = FieldHelper::safe($query[1]);
// make the table name save
$table = StringHelper::safe($query[0]);
// reverse placeholder as much as we can
$code = $this->reverse->engine(
$code, $placeholders, $target, $id, $field, $table, $use_statements
);
// update the GUI/Tables/Database
$object = new \stdClass();
$object->id = $id;
$object->{$field} = base64_encode(
(string) $code
); // (TODO) this may not always work...
// update the value in GUI
$this->db->updateObject(
'#__componentbuilder_' . (string) $table, $object, 'id'
);
}
}
}
}
/**
* search a code to see if there is already any custom
* code or other reasons not to add the GUI code placeholders
*
* @param string $code The code to check
*
* @return bool true if GUI code placeholders can be added
* @since 3.2.0
*/
protected function check(string &$code): bool
{
// check for customcode placeholders
// we do not add GUI wrapper placeholder to code
// that already has any customcode placeholders
return strpos($code, '$$$$') === false;
}
}

View File

@@ -0,0 +1,111 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace VDM\Joomla\Componentbuilder\Compiler\Customcode;
use VDM\Joomla\Utilities\GetHelper;
use VDM\Joomla\Utilities\FileHelper;
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
/**
* Compiler Custom Code MD5
*
* @since 3.2.0
*/
class Hash
{
/**
* Compiler Placeholder
*
* @var Placeholder
* @since 3.2.0
**/
protected Placeholder $placeholder;
/**
* Constructor.
*
* @param Placeholder|null $placeholder The compiler placeholder object.
*
* @since 3.2.0
*/
public function __construct(?Placeholder $placeholder = null)
{
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
}
/**
* Set the MD5 hashed string or file or string
*
* @param string $script The code string
*
* @return string
* @since 3.2.0
*/
public function set(string $script): string
{
// check if we should hash a string
if (strpos($script, 'HASH' . 'STRING((((') !== false)
{
// get the strings
$values = GetHelper::allBetween(
$script, 'HASH' . 'STRING((((', '))))'
);
$locker = [];
// convert them
foreach ($values as $value)
{
$locker['HASH' . 'STRING((((' . $value . '))))']
= md5((string) $value);
}
// update the script
return $this->placeholder->update($script, $locker);
}
// check if we should hash a file
if (strpos($script, 'HASH' . 'FILE((((') !== false)
{
// get the strings
$values = GetHelper::allBetween(
$script, 'HASH' . 'FILE((((', '))))'
);
$locker = [];
// convert them
foreach ($values as $path)
{
// we first get the file if it exists
if (FileHelper::exists($path) && $value = FileHelper::getContent($path))
{
// now we hash the file content
$locker['HASH' . 'FILE((((' . $path . '))))']
= md5((string) $value);
}
else
{
// could not retrieve the file so we show error
$locker['HASH' . 'FILE((((' . $path . '))))']
= 'ERROR';
}
}
// update the script
return $this->placeholder->update($script, $locker);
}
return $script;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/**
* @package Joomla.Component.Builder
*
* @created 4th September, 2022
* @author Llewellyn van der Merwe <https://dev.vdm.io>
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace VDM\Joomla\Componentbuilder\Compiler\Customcode;
use VDM\Joomla\Utilities\GetHelper;
use VDM\Joomla\Componentbuilder\Compiler\Utilities\Indent;
use VDM\Joomla\Componentbuilder\Compiler\Factory as Compiler;
use VDM\Joomla\Componentbuilder\Compiler\Placeholder;
use VDM\Joomla\Componentbuilder\Compiler\Interfaces\Customcode\LockBaseInterface;
/**
* Compiler Custom Code Base64
*
* @since 3.2.0
*/
class LockBase implements LockBaseInterface
{
/**
* Compiler Placeholder
*
* @var Placeholder
* @since 3.2.0
**/
protected Placeholder $placeholder;
/**
* Constructor.
*
* @param Placeholder|null $placeholder The compiler placeholder object.
*
* @since 3.2.0
*/
public function __construct(?Placeholder $placeholder = null)
{
$this->placeholder = $placeholder ?: Compiler::_('Placeholder');
}
/**
* Set a string as bsae64 (basic)
*
* @param string $script The code string
*
* @return string
* @since 3.2.0
*/
public function set(string $script): string
{
if (\strpos($script, 'LOCK'.'BASE64((((') !== false)
{
// get the strings
$values = GetHelper::allBetween(
$script, 'LOCK'.'BASE64((((', '))))'
);
$locker = [];
// convert them
foreach ($values as $value)
{
$locker['LOCK'.'BASE64((((' . $value . '))))']
= "base64_decode( preg_replace('/\s+/', ''," .
PHP_EOL . Indent::_(2) . "'" .
wordwrap(
base64_encode((string) $value), 64, PHP_EOL . Indent::_(2), true
) .
"'))";
}
// update the script
return $this->placeholder->update($script, $locker);
}
return $script;
}
}

View File

@@ -0,0 +1 @@
<html><body bgcolor="#FFFFFF"></body></html>