diff --git a/CHANGELOG.md b/CHANGELOG.md index a1418004d..172585f23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# v5.0.3-alpha1 + +- Add push option to powers area + # v5.0.2 - Fix site view form missing classes in J4+ diff --git a/ComponentbuilderInstallerScript.php b/ComponentbuilderInstallerScript.php index 52480e958..0d3575743 100644 --- a/ComponentbuilderInstallerScript.php +++ b/ComponentbuilderInstallerScript.php @@ -1514,7 +1514,7 @@ class Com_ComponentbuilderInstallerScript implements InstallerScriptInterface // Fix the assets table rules column size. - $this->setDatabaseAssetsRulesFix(95040, "MEDIUMTEXT"); + $this->setDatabaseAssetsRulesFix(95200, "MEDIUMTEXT"); // Install the global extension params. $this->setExtensionsParams( '{"autorName":"Llewellyn van der Merwe","autorEmail":"joomla@vdm.io","subform_layouts":"default","editor":"none","manage_jcb_package_directories":"2","set_browser_storage":"1","storage_time_to_live":"global","super_powers_documentation":"0","powers_repository":"0","super_powers_repositories":"0","builder_gif_size":"480-272","compiler_plugin":["componentbuilderactionlogcompiler","componentbuilderfieldorderingcompiler","componentbuilderheaderscompiler","componentbuilderpowersautoloadercompiler","componentbuilderprivacycompiler"],"add_menu_prefix":"1","menu_prefix":"ยป","namespace_prefix":"JCB","minify":"0","language":"en-GB","percentagelanguageadd":"30","assets_table_fix":"2","compiler_field_builder_type":"2","field_name_builder":"1","type_name_builder":"1","import_guid_only":"1","export_language_strings":"1","cronjob_backup_type":"1","cronjob_backup_server":"0","backup_package_name":"JCB_Backup_[YEAR]_[MONTH]_[DAY]","export_company":"Vast Development Method","export_owner":"Llewellyn van der Merwe","export_email":"joomla@vdm.io","export_website":"https://dev.vdm.io/","export_license":"GNU/GPL Version 2 or later - http://www.gnu.org/licenses/gpl-2.0.html","export_copyright":"Copyright (C) 2015. All Rights Reserved","check_in":"-1 day","save_history":"1","history_limit":"10","add_jquery_framework":"1","uikit_load":"1","uikit_min":"","uikit_style":""}' @@ -3270,7 +3270,7 @@ class Com_ComponentbuilderInstallerScript implements InstallerScriptInterface echo '
'; +You can add these repository-specific placeholders here. Please note that these placeholders are only utilized during direct pushes from the powers area and are not considered during the compilation of any component. Although you can use the same placeholders to ensure consistent outcomes, they technically have no direct relationship with the components.
" +COM_COMPONENTBUILDER_REPOSITORY_PLACEHOLDER_NOTE_LABEL="The Repository Placeholders" COM_COMPONENTBUILDER_REPOSITORY_PUBLISHING="Publishing" COM_COMPONENTBUILDER_REPOSITORY_READ_BRANCH_DESCRIPTION="The read branch to use in the API calls." COM_COMPONENTBUILDER_REPOSITORY_READ_BRANCH_HINT="[master]" @@ -8020,7 +8027,9 @@ COM_COMPONENTBUILDER_REPOSITORY_SYSTEM_NAME_HINT="The System Name Here" COM_COMPONENTBUILDER_REPOSITORY_SYSTEM_NAME_LABEL="System Name" COM_COMPONENTBUILDER_REPOSITORY_SYSTEM_NAME_MESSAGE="Error! Please add some text here." COM_COMPONENTBUILDER_REPOSITORY_TARGET_DESCRIPTION="The target content this repository holds." +COM_COMPONENTBUILDER_REPOSITORY_TARGET_HINT="coral" COM_COMPONENTBUILDER_REPOSITORY_TARGET_LABEL="Target Content" +COM_COMPONENTBUILDER_REPOSITORY_TARGET_MESSAGE="Error! That target placeholder text already exist. Please add an unique placeholder target." COM_COMPONENTBUILDER_REPOSITORY_TOKEN_DESCRIPTION="This token is used to access the api." COM_COMPONENTBUILDER_REPOSITORY_TOKEN_HINT="Access Tokens" COM_COMPONENTBUILDER_REPOSITORY_TOKEN_LABEL="Access Token" @@ -8030,6 +8039,10 @@ COM_COMPONENTBUILDER_REPOSITORY_USERNAME_DESCRIPTION="The username to use in the COM_COMPONENTBUILDER_REPOSITORY_USERNAME_HINT="Username" COM_COMPONENTBUILDER_REPOSITORY_USERNAME_LABEL="Username" COM_COMPONENTBUILDER_REPOSITORY_USERNAME_MESSAGE="Error! Please add username here." +COM_COMPONENTBUILDER_REPOSITORY_VALUE_DESCRIPTION="Set the text you would like to set as the replacement value for the targeted placeholder." +COM_COMPONENTBUILDER_REPOSITORY_VALUE_HINT="membersmanager" +COM_COMPONENTBUILDER_REPOSITORY_VALUE_LABEL="Set String Value" +COM_COMPONENTBUILDER_REPOSITORY_VALUE_MESSAGE="Error! Please add some set target value here." COM_COMPONENTBUILDER_REPOSITORY_VERSION_DESC="A count of the number of times this Repository has been revised." COM_COMPONENTBUILDER_REPOSITORY_VERSION_LABEL="Version" COM_COMPONENTBUILDER_REPOSITORY_WRITE_BRANCH_DESCRIPTION="The write branch to use in the API calls. This can be the same as the read branch." @@ -9039,6 +9052,7 @@ COM_COMPONENTBUILDER_THESE_JOOMLA_POWERS_HAVE_SUCCESSFULLY_BEEN_PUSHED="These Jo COM_COMPONENTBUILDER_THESE_JOOMLA_POWERS_HAVE_SUCCESSFULLY_BEEN_RESET="These Joomla powers have successfully been reset." COM_COMPONENTBUILDER_THESE_OPTIONS_ARE_NOT_AVAILABLE_TO_THE_FIELD_IF_BNONE_DBB_OPTION_IS_SELECTED="These options are not available to the field if None DB option is selected." COM_COMPONENTBUILDER_THESE_OPTIONS_ARE_ONLY_AVAILABLE_TO_THE_FIELD_IF_BSHOW_IN_LIST_VIEWB_OPTION_IS_SELECTED="These options are only available to the field if Show in list view option is selected." +COM_COMPONENTBUILDER_THESE_POWERS_HAVE_SUCCESSFULLY_BEEN_PUSHED="These powers have successfully been pushed." COM_COMPONENTBUILDER_THESE_POWERS_HAVE_SUCCESSFULLY_BEEN_RESET="These powers have successfully been reset." COM_COMPONENTBUILDER_THE_ADMIN_CUSTOM_TABS="The admin custom tabs" COM_COMPONENTBUILDER_THE_ADMIN_FIELDS="The admin fields" @@ -9103,12 +9117,15 @@ COM_COMPONENTBUILDER_THE_PACKAGE_KEY_IS_CODESCODE="The package key is:%s<
COM_COMPONENTBUILDER_THE_PACKAGE_KEY_IS_S="The package key is: %s"
COM_COMPONENTBUILDER_THE_PLUGIN_FILES_FOLDERS="The plugin files & folders"
COM_COMPONENTBUILDER_THE_PLUGIN_UPDATES="The plugin updates"
+COM_COMPONENTBUILDER_THE_POWER_HAS_SUCCESSFULLY_BEEN_PUSHED="The power has successfully been pushed."
COM_COMPONENTBUILDER_THE_POWER_HAS_SUCCESSFULLY_BEEN_RESET="The power has successfully been reset."
COM_COMPONENTBUILDER_THE_PRIVATE_KEY_FIELD_COULD_NOT_BE_LOADED_FOR_BSB_SERVER="The private key field could not be loaded for %s server!"
COM_COMPONENTBUILDER_THE_PRIVATE_KEY_FILE_COULD_NOT_BE_LOADEDFOUND_FOR_BSB_SERVER="The private key file could not be loaded/found for %s server!"
COM_COMPONENTBUILDER_THE_PRO_BOARD_IS_LOADING="The pro board is loading"
COM_COMPONENTBUILDER_THE_PUSH_OF_THESE_JOOMLA_POWERS_HAS_FAILED="The push of these Joomla powers has failed."
+COM_COMPONENTBUILDER_THE_PUSH_OF_THESE_POWERS_HAS_FAILED="The push of these powers has failed."
COM_COMPONENTBUILDER_THE_PUSH_OF_THIS_JOOMLA_POWER_HAS_FAILED="The push of this Joomla power has failed."
+COM_COMPONENTBUILDER_THE_PUSH_OF_THIS_POWER_HAS_FAILED="The push of this power has failed."
COM_COMPONENTBUILDER_THE_README_IS_LOADING="The readme is loading"
COM_COMPONENTBUILDER_THE_REPLACE_PROCESS_HAD_AN_ERROR_WITH_TABLE="The replace process had an error with table"
COM_COMPONENTBUILDER_THE_RESET_OF_THESE_JOOMLA_POWERS_HAS_FAILED="The reset of these Joomla powers has failed."
@@ -9365,6 +9382,7 @@ COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_INSTALL_THE_COMPONENT="You do
COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_INSTALL_THE_MODULE="You do not have permission to install the module!"
COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_INSTALL_THE_PLUGIN="You do not have permission to install the plugin!"
COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_PUSH_THIS_JOOMLA_POWER="You do not have permission to push this Joomla power"
+COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_PUSH_THIS_POWER="You do not have permission to push this power"
COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_RESET_THIS_JOOMLA_POWER="You do not have permission to reset this Joomla power"
COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_RESET_THIS_POWER="You do not have permission to reset this power"
COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_RUN_THE_GET_BOILERPLATE_MODULE="You do not have permission to run the get boilerplate module!"
diff --git a/admin/language/en-GB/en-GB.com_componentbuilder.sys.ini b/admin/language/en-GB/en-GB.com_componentbuilder.sys.ini
index 901603dde..4073ceeed 100644
--- a/admin/language/en-GB/en-GB.com_componentbuilder.sys.ini
+++ b/admin/language/en-GB/en-GB.com_componentbuilder.sys.ini
@@ -981,6 +981,8 @@ COM_COMPONENTBUILDER_POWERS_SUBMENU="Powers Submenu"
COM_COMPONENTBUILDER_POWERS_SUBMENU_DESC="Allows the users in this group to submenu of power"
COM_COMPONENTBUILDER_POWER_INIT_BUTTON_ACCESS="Power Init Button Access"
COM_COMPONENTBUILDER_POWER_INIT_BUTTON_ACCESS_DESC="Allows the users in this group to access the init button."
+COM_COMPONENTBUILDER_POWER_PUSH_BUTTON_ACCESS="Power Push Button Access"
+COM_COMPONENTBUILDER_POWER_PUSH_BUTTON_ACCESS_DESC="Allows the users in this group to access the push button."
COM_COMPONENTBUILDER_POWER_RESET_BUTTON_ACCESS="Power Reset Button Access"
COM_COMPONENTBUILDER_POWER_RESET_BUTTON_ACCESS_DESC="Allows the users in this group to access the reset button."
COM_COMPONENTBUILDER_REPOSITORIES_ACCESS="Repositories Access"
diff --git a/admin/layouts/repository/placeholders_fullwidth.php b/admin/layouts/repository/placeholders_fullwidth.php
new file mode 100644
index 000000000..d89b53381
--- /dev/null
+++ b/admin/layouts/repository/placeholders_fullwidth.php
@@ -0,0 +1,45 @@
+
+ * @git 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
+ */
+
+// No direct access to this file
+defined('_JEXEC') or die;
+
+// get the form
+$form = $displayData->getForm();
+
+// get the layout fields override method name (from layout path/ID)
+$layout_path_array = explode('.', $this->getLayoutId());
+// Since we cannot pass the layout and tab names as parameters to the model method
+// this name combination of tab and layout in the method name is the only work around
+// seeing that JCB uses those two values (tab_name & layout_name) as the layout file name.
+// example of layout name: details_left.php
+// example of method name: getFields_details_left()
+$fields_tab_layout = 'fields_' . $layout_path_array[1];
+
+// get the fields
+$fields = $displayData->get($fields_tab_layout) ?: array(
+ 'placeholder_note',
+ 'addplaceholders'
+);
+
+$hiddenFields = $displayData->get('hidden_fields') ?: [];
+
+?>
+
+
+
+
+ setFieldAttribute($field, 'type', 'hidden'); ?>
+
+ renderField($field, null, null, array('class' => 'control-wrapper-' . $field)); ?>
+
+
+
diff --git a/admin/sql/install.mysql.utf8.sql b/admin/sql/install.mysql.utf8.sql
index c4c0ce86e..838074795 100644
--- a/admin/sql/install.mysql.utf8.sql
+++ b/admin/sql/install.mysql.utf8.sql
@@ -1302,6 +1302,7 @@ CREATE TABLE IF NOT EXISTS `#__componentbuilder_repository` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`asset_id` INT(10) unsigned NOT NULL DEFAULT 0 COMMENT 'FK to the #__assets table.',
`access_repo` TINYINT(1) NOT NULL DEFAULT 0,
+ `addplaceholders` TEXT NOT NULL,
`base` VARCHAR(255) NOT NULL DEFAULT '',
`guid` VARCHAR(36) NOT NULL DEFAULT '',
`organisation` VARCHAR(255) NOT NULL DEFAULT '',
@@ -2517,8 +2518,8 @@ INSERT INTO `#__componentbuilder_language` (`id`, `langtag`, `name`, `params`, `
INSERT INTO `#__componentbuilder_repository` (`id`, `system_name`, `organisation`, `repository`, `target`, `type`, `base`, `guid`, `read_branch`, `access`, `ordering`, `published`, `modified`, `created`, `params`) VALUES
(1, 'Super Powers', 'joomla', 'super-powers', 1, 1, 'https://git.vdm.dev', '1a1b6f3d-f16c-431a-b270-7a8a80cc7f56', 'master', 1, 1, 1, '2024-06-10 11:13:46', '2024-06-10 10:59:35', ''),
-(2, 'JCB Compiler', 'joomla', 'jcb-compiler', 1, 1, 'https://git.vdm.dev', 'd12c7672-7b75-4a59-a5f3-c33f1a2f929b', 'master', 1, 2, 1, '2024-06-15 17:43:09', '2024-06-10 11:01:06', ''),
-(3, 'JCB Packager', 'joomla', 'jcb-packager', 1, 1, 'https://git.vdm.dev', 'd8155014-5734-4fb5-9ccb-6bb33367ef78', 'master', 1, 3, 1, '2024-06-15 17:43:09', '2024-06-10 11:01:35', ''),
+(2, 'JCB Compiler', 'joomla', 'jcb-compiler', 1, 1, 'https://git.vdm.dev', 'd12c7672-7b75-4a59-a5f3-c33f1a2f929b', 'master', 1, 2, 1, '2024-08-20 09:34:58', '2024-06-10 11:01:06', ''),
+(3, 'JCB Packager', 'joomla', 'jcb-packager', 1, 1, 'https://git.vdm.dev', 'd8155014-5734-4fb5-9ccb-6bb33367ef78', 'master', 1, 3, 1, '2024-08-20 08:33:21', '2024-06-10 11:01:35', ''),
(4, 'PHP Seclib', 'joomla', 'phpseclib', 1, 1, 'https://git.vdm.dev', 'e63d916c-81c1-4a56-a73f-584174b36c87', 'master', 1, 4, 1, '2024-06-15 17:43:09', '2024-06-10 11:02:03', ''),
(5, 'JCB Search', 'joomla', 'search', 1, 1, 'https://git.vdm.dev', '6e57dc23-a17d-4937-bdbf-639ae1b8409f', 'master', 1, 5, 1, '2024-06-15 17:43:09', '2024-06-10 11:02:43', ''),
(6, 'Gitea', 'joomla', 'gitea', 1, 1, 'https://git.vdm.dev', 'a0cbfb8a-b427-48eb-b493-a02b34854019', 'master', 1, 6, 1, '2024-06-15 17:43:09', '2024-06-10 11:03:09', ''),
diff --git a/admin/sql/updates/mysql/5.0.2.sql b/admin/sql/updates/mysql/5.0.2.sql
new file mode 100644
index 000000000..8b1378917
--- /dev/null
+++ b/admin/sql/updates/mysql/5.0.2.sql
@@ -0,0 +1 @@
+
diff --git a/admin/src/Controller/Joomla_powerController.php b/admin/src/Controller/Joomla_powerController.php
index bc8d98f7c..4679c1c01 100644
--- a/admin/src/Controller/Joomla_powerController.php
+++ b/admin/src/Controller/Joomla_powerController.php
@@ -105,7 +105,7 @@ class Joomla_powerController extends FormController
$message = '' . Text::_('COM_COMPONENTBUILDER_NOT_SAVED') . '
';
$message .= '' . Text::_('COM_COMPONENTBUILDER_YOU_MUST_FIRST_SAVE_THE_JOOMLA_POWER_BEFORE_YOU_CAN_USE_THIS_FEATURE') . '
';
}
- elseif($user->authorise('power.reset', 'com_componentbuilder'))
+ elseif($user->authorise('joomla_power.reset', 'com_componentbuilder'))
{
try {
if (JoomlaPowerFactory::_('Joomla.Power.Remote.Get')->reset([$guid]))
@@ -180,7 +180,7 @@ class Joomla_powerController extends FormController
$message = '' . Text::_('COM_COMPONENTBUILDER_NOT_SAVED') . '
';
$message .= '' . Text::_('COM_COMPONENTBUILDER_YOU_MUST_FIRST_SAVE_THE_JOOMLA_POWER_BEFORE_YOU_CAN_USE_THIS_FEATURE') . '
';
}
- elseif($user->authorise('power.push', 'com_componentbuilder'))
+ elseif($user->authorise('joomla_power.push', 'com_componentbuilder'))
{
try {
if (JoomlaPowerFactory::_('Joomla.Power.Remote.Set')->items([$guid]))
diff --git a/admin/src/Controller/Joomla_powersController.php b/admin/src/Controller/Joomla_powersController.php
index ba19d5cfb..8c73d07f3 100644
--- a/admin/src/Controller/Joomla_powersController.php
+++ b/admin/src/Controller/Joomla_powersController.php
@@ -80,7 +80,7 @@ class Joomla_powersController extends AdminController
$status = 'error';
$success = false;
- if($user->authorise('power.init', 'com_componentbuilder'))
+ if($user->authorise('joomla_power.init', 'com_componentbuilder'))
{
try {
if (JoomlaPowerFactory::_('Joomla.Power.Remote.Get')->init())
@@ -153,7 +153,7 @@ class Joomla_powersController extends AdminController
// check if user has the right
$user = Factory::getUser();
- if($user->authorise('power.reset', 'com_componentbuilder'))
+ if($user->authorise('joomla_power.reset', 'com_componentbuilder'))
{
$guids = GetHelper::vars('joomla_power', $pks, 'id', 'guid');
@@ -232,7 +232,7 @@ class Joomla_powersController extends AdminController
// check if user has the right
$user = Factory::getUser();
- if($user->authorise('power.push', 'com_componentbuilder'))
+ if($user->authorise('joomla_power.push', 'com_componentbuilder'))
{
$guids = GetHelper::vars('joomla_power', $pks, 'id', 'guid');
diff --git a/admin/src/Controller/PowerController.php b/admin/src/Controller/PowerController.php
index 9a3cbae99..d9fd5173b 100644
--- a/admin/src/Controller/PowerController.php
+++ b/admin/src/Controller/PowerController.php
@@ -130,6 +130,81 @@ class PowerController extends FormController
$this->setRedirect($redirect_url, $message, $status);
+ return $success;
+ }
+
+ /**
+ * Pushes the specified power.
+ *
+ * This function performs several checks and operations:
+ * 1. It verifies the authenticity of the request to prevent request forgery.
+ * 2. It retrieves the item data posted by the user.
+ * 3. It checks whether the current user has the necessary permissions to push the power.
+ * 4. It validates the presence of the necessary item identifiers (ID and GUID).
+ * 5. If the user is authorized and the identifiers are valid, it attempts to push the specified power.
+ * 6. Depending on the result of the push operation, it sets the appropriate success or error message.
+ * 7. It redirects the user to a specified URL with the result message and status.
+ *
+ * @return bool True on successful push, false on failure.
+ */
+ public function pushPowers()
+ {
+ // Check for request forgeries
+ Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
+
+ // get Item posted
+ $item = $this->input->post->get('jform', array(), 'array');
+
+ // check if user has the right
+ $user = Factory::getUser();
+
+ // set default error message
+ $message = '' . Text::_('COM_COMPONENTBUILDER_PERMISSION_DENIED') . '
';
+ $message .= '' . Text::_('COM_COMPONENTBUILDER_YOU_DO_NOT_HAVE_PERMISSION_TO_PUSH_THIS_POWER') . '
';
+ $status = 'error';
+ $success = false;
+
+ // load the ID
+ $id = $item['id'] ?? null;
+ $guid = $item['guid'] ?? null;
+
+ // check if there is any selections
+ if ($id === null || $guid === null)
+ {
+ // set error message
+ $message = '' . Text::_('COM_COMPONENTBUILDER_NOT_SAVED') . '
';
+ $message .= '' . Text::_('COM_COMPONENTBUILDER_YOU_MUST_FIRST_SAVE_THE_POWER_BEFORE_YOU_CAN_USE_THIS_FEATURE') . '
';
+ }
+ elseif($user->authorise('power.push', 'com_componentbuilder'))
+ {
+ try {
+ if (PowerFactory::_('Power.Remote.Set')->items([$guid]))
+ {
+ // set success message
+ $message = ''.Text::_('COM_COMPONENTBUILDER_SUCCESS').'
';
+ $message .= ''.Text::_('COM_COMPONENTBUILDER_THE_POWER_HAS_SUCCESSFULLY_BEEN_PUSHED').'
';
+ $status = 'success';
+ $success = true;
+ }
+ else
+ {
+ $message = '' . Text::_('COM_COMPONENTBUILDER_PUSH_FAILED') . '
';
+ $message .= '' . Text::_('COM_COMPONENTBUILDER_THE_PUSH_OF_THIS_POWER_HAS_FAILED') . '
';
+ }
+ } catch (\Exception $e) {
+ $message = '' . Text::_('COM_COMPONENTBUILDER_PUSH_FAILED') . '
';
+ $message .= '' . \htmlspecialchars($e->getMessage()) . '
';
+ }
+ }
+
+ // set redirect
+ $redirect_url = Route::_(
+ 'index.php?option=com_componentbuilder&view=power'
+ . $this->getRedirectToItemAppend($id), $success
+ );
+
+ $this->setRedirect($redirect_url, $message, $status);
+
return $success;
}
diff --git a/admin/src/Controller/PowersController.php b/admin/src/Controller/PowersController.php
index 2575787f6..37184982b 100644
--- a/admin/src/Controller/PowersController.php
+++ b/admin/src/Controller/PowersController.php
@@ -173,6 +173,85 @@ class PowersController extends AdminController
return $success;
}
+ // set redirect
+ $redirect_url = Route::_('index.php?option=com_componentbuilder&view=powers', false);
+ $this->setRedirect($redirect_url);
+ return $success;
+ }
+
+ /**
+ * Pushes the selected powers.
+ *
+ * This function performs several checks and operations:
+ * 1. It verifies the authenticity of the request to prevent request forgery.
+ * 2. It retrieves the IDs of the selected powers from the user input.
+ * 3. It sanitizes the input by converting the IDs to integers.
+ * 4. It checks whether any powers have been selected.
+ * 5. It checks whether the current user has the necessary permissions to push the selected powers.
+ * 6. If the user is authorized and powers are selected, it attempts to push the selected powers.
+ * 7. Depending on the result of the push operation, it sets the appropriate success or error message.
+ * 8. It redirects the user to a specified URL with the result message and status.
+ *
+ * @return bool True on successful push, false on failure.
+ */
+ public function pushPowers()
+ {
+ // Check for request forgeries
+ Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
+
+ // get IDS of the selected powers
+ $pks = $this->input->post->get('cid', [], 'array');
+
+ // Sanitize the input
+ ArrayHelper::toInteger($pks);
+
+ // check if there is any selections
+ if ($pks === [])
+ {
+ // set error message
+ $message = ''.Text::_('COM_COMPONENTBUILDER_NO_SELECTION_DETECTED').'
';
+ $message .= ''.Text::_('COM_COMPONENTBUILDER_PLEASE_FIRST_MAKE_A_SELECTION_FROM_THE_LIST').'
';
+ // set redirect
+ $redirect_url = Route::_('index.php?option=com_componentbuilder&view=powers', false);
+ $this->setRedirect($redirect_url, $message, 'error');
+ return false;
+ }
+
+ $status = 'error';
+ $success = false;
+
+ // check if user has the right
+ $user = Factory::getUser();
+ if($user->authorise('power.push', 'com_componentbuilder'))
+ {
+ $guids = GetHelper::vars('power', $pks, 'id', 'guid');
+
+ try {
+ if (PowerFactory::_('Power.Remote.Set')->items($guids))
+ {
+ // set success message
+ $message = ''.Text::_('COM_COMPONENTBUILDER_SUCCESS').'
';
+ $message .= ''.Text::_('COM_COMPONENTBUILDER_THESE_POWERS_HAVE_SUCCESSFULLY_BEEN_PUSHED').'
';
+ $status = 'success';
+ $success = true;
+ }
+ else
+ {
+ $message = '' . Text::_('COM_COMPONENTBUILDER_PUSH_FAILED') . '
';
+ $message .= '' . Text::_('COM_COMPONENTBUILDER_THE_PUSH_OF_THESE_POWERS_HAS_FAILED') . '
';
+ }
+ } catch (\Exception $e) {
+ $message = '' . Text::_('COM_COMPONENTBUILDER_PUSH_FAILED') . '
';
+ $message .= '' . \htmlspecialchars($e->getMessage()) . '
';
+ }
+
+ // set redirect
+ $redirect_url = Route::_('index.php?option=com_componentbuilder&view=powers', $success);
+ $this->setRedirect($redirect_url, $message, $status);
+
+ return $success;
+ }
+
// set redirect
$redirect_url = Route::_('index.php?option=com_componentbuilder&view=powers', false);
$this->setRedirect($redirect_url);
diff --git a/admin/src/Model/RepositoryModel.php b/admin/src/Model/RepositoryModel.php
index e43cb8312..1124d5a05 100644
--- a/admin/src/Model/RepositoryModel.php
+++ b/admin/src/Model/RepositoryModel.php
@@ -71,6 +71,12 @@ class RepositoryModel extends AdminModel
'target',
'access_repo'
)
+ ),
+ 'placeholders' => array(
+ 'fullwidth' => array(
+ 'placeholder_note',
+ 'addplaceholders'
+ )
)
);
@@ -154,6 +160,14 @@ class RepositoryModel extends AdminModel
$registry->loadString($item->metadata);
$item->metadata = $registry->toArray();
}
+
+ if (!empty($item->addplaceholders))
+ {
+ // Convert the addplaceholders field to an array.
+ $addplaceholders = new Registry;
+ $addplaceholders->loadString($item->addplaceholders);
+ $item->addplaceholders = $addplaceholders->toArray();
+ }
}
return $item;
@@ -893,6 +907,19 @@ class RepositoryModel extends AdminModel
$data['guid'] = (string) GuidHelper::get();
}
+ // Set the addplaceholders items to data.
+ if (isset($data['addplaceholders']) && is_array($data['addplaceholders']))
+ {
+ $addplaceholders = new Registry;
+ $addplaceholders->loadArray($data['addplaceholders']);
+ $data['addplaceholders'] = (string) $addplaceholders;
+ }
+ elseif (!isset($data['addplaceholders']))
+ {
+ // Set the empty addplaceholders to data
+ $data['addplaceholders'] = '';
+ }
+
// Set the Params Items to data
if (isset($data['params']) && is_array($data['params']))
{
diff --git a/admin/src/View/Power/HtmlView.php b/admin/src/View/Power/HtmlView.php
index db1493398..331732f81 100644
--- a/admin/src/View/Power/HtmlView.php
+++ b/admin/src/View/Power/HtmlView.php
@@ -258,6 +258,11 @@ class HtmlView extends BaseHtmlView
// add Reset button.
ToolbarHelper::custom('power.resetPowers', 'joomla custom-button-resetpowers', '', 'COM_COMPONENTBUILDER_RESET', false);
}
+ if ($this->canDo->get('power.push'))
+ {
+ // add Push button.
+ ToolbarHelper::custom('power.pushPowers', 'share custom-button-pushpowers', '', 'COM_COMPONENTBUILDER_PUSH', false);
+ }
ToolbarHelper::cancel('power.cancel', 'JTOOLBAR_CLOSE');
}
}
diff --git a/admin/src/View/Powers/HtmlView.php b/admin/src/View/Powers/HtmlView.php
index 1184fcd80..0e270d045 100644
--- a/admin/src/View/Powers/HtmlView.php
+++ b/admin/src/View/Powers/HtmlView.php
@@ -208,6 +208,11 @@ class HtmlView extends BaseHtmlView
// add Reset button.
ToolbarHelper::custom('powers.resetPowers', 'joomla custom-button-resetpowers', '', 'COM_COMPONENTBUILDER_RESET', false);
}
+ if ($this->user->authorise('power.push', 'com_componentbuilder'))
+ {
+ // add Push button.
+ ToolbarHelper::custom('powers.pushPowers', 'share custom-button-pushpowers', '', 'COM_COMPONENTBUILDER_PUSH', false);
+ }
// set help url for this view if found
$this->help_url = ComponentbuilderHelper::getHelpUrl('powers');
diff --git a/admin/tmpl/repository/default.php b/admin/tmpl/repository/default.php
index 76df3b9ad..011cdece6 100644
--- a/admin/tmpl/repository/default.php
+++ b/admin/tmpl/repository/default.php
@@ -59,6 +59,16 @@ defined('_JEXEC') or die;
+
+
+
+
+
+
+
+
+
+
ignore_fieldsets = array('details','metadata','vdmmetadata','accesscontrol'); ?>
tab_name = 'repositoryTab'; ?>
diff --git a/componentbuilder.xml b/componentbuilder.xml
index 064c83bc0..6029eb838 100644
--- a/componentbuilder.xml
+++ b/componentbuilder.xml
@@ -1,15 +1,15 @@
COM_COMPONENTBUILDER
- 16th August, 2024
+ 20th August, 2024
Llewellyn van der Merwe
joomla@vdm.io
https://dev.vdm.io
Copyright (C) 2015 Vast Development Method. All rights reserved.
GNU General Public License version 2 or later; see LICENSE.txt
- 5.0.2
+ 5.0.3-alpha1
Component Builder (v.5.0.2)
+ Component Builder (v.5.0.3-alpha1)
The Component Builder for [Joomla](https://extensions.joomla.org/extension/component-builder/) is highly advanced tool that is truly able to build extremely complex components in a fraction of the time.
diff --git a/componentbuilder_update_server.xml b/componentbuilder_update_server.xml
index ad51fe7c7..23b5d32cc 100644
--- a/componentbuilder_update_server.xml
+++ b/componentbuilder_update_server.xml
@@ -75,7 +75,7 @@
Component Builder
Builds Complex Joomla Components
pkg_component_builder
- component
+ package
site
5.0.2
https://dev.vdm.io
@@ -89,4 +89,22 @@
https://dev.vdm.io
+
+ Component Builder
+ Builds Complex Joomla Components
+ pkg_component_builder
+ package
+ site
+ 5.0.3-alpha1
+ https://dev.vdm.io
+
+ https://git.vdm.dev/api/v1/repos/joomla/pkg-component-builder/archive/v5.0.3-alpha1.zip
+
+
+ alpha
+
+ Llewellyn van der Merwe
+ https://dev.vdm.io
+
+
\ No newline at end of file
diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Remote/Set.php b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Remote/Set.php
index 51256f8d7..039a8d82f 100644
--- a/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Remote/Set.php
+++ b/libraries/vendor_jcb/VDM.Joomla/src/Abstraction/Remote/Set.php
@@ -17,6 +17,7 @@ use VDM\Joomla\Interfaces\Data\ItemsInterface as Items;
use VDM\Joomla\Interfaces\Readme\ItemInterface as ItemReadme;
use VDM\Joomla\Interfaces\Readme\MainInterface as MainReadme;
use VDM\Joomla\Interfaces\Git\Repository\ContentsInterface as Git;
+use VDM\Joomla\Utilities\ObjectHelper;
use VDM\Joomla\Interfaces\Remote\SetInterface;
@@ -147,6 +148,27 @@ abstract class Set implements SetInterface
*/
protected string $index_settings_path = 'index.json';
+ /**
+ * Core Placeholders
+ *
+ * @var array
+ * @since 5.0.3
+ */
+ protected array $placeholders = [
+ '[['.'[NamespacePrefix]]]' => 'VDM',
+ '[['.'[ComponentNamespace]]]' => 'Componentbuilder',
+ '[['.'[Component]]]' => 'Componentbuilder',
+ '[['.'[component]]]' => 'componentbuilder'
+ ];
+
+ /**
+ * Repo Placeholders
+ *
+ * @var array
+ * @since 5.0.3
+ */
+ protected array $repoPlaceholders = [];
+
/**
* Constructor.
*
@@ -499,55 +521,10 @@ abstract class Set implements SetInterface
* @since 3.2.2
*/
protected function getLocalItems(array $guids): ?array
- {
- $items = $this->fetchLocalItems($guids);
-
- if ($items === null)
- {
- return null;
- }
-
- return $this->mapItems($items);
- }
-
- /**
- * Fetch items from the database
- *
- * @param array $guids The global unique ids of the items
- *
- * @return array|null
- * @since 3.2.2
- */
- protected function fetchLocalItems(array $guids): ?array
{
return $this->items->table($this->getTable())->get($guids);
}
- /**
- * Map items to their properties
- *
- * @param array $items The items fetched from the database
- *
- * @return array
- * @since 3.2.2
- */
- protected function mapItems(array $items): array
- {
- $bucket = [];
-
- foreach ($items as $item)
- {
- if (!isset($item->guid))
- {
- continue;
- }
-
- $bucket[$item->guid] = $this->mapItem($item);
- }
-
- return $bucket;
- }
-
/**
* Map a single item to its properties
*
@@ -562,7 +539,15 @@ abstract class Set implements SetInterface
foreach ($this->map as $key => $map)
{
- $power[$key] = $item->{$map} ?? null;
+ $methodName = "mapItemValue_{$key}";
+ if (method_exists($this, $methodName))
+ {
+ $this->{$methodName}($item, $power);
+ }
+ else
+ {
+ $power[$key] = $item->{$map} ?? null;
+ }
}
return (object) $power;
@@ -586,11 +571,15 @@ abstract class Set implements SetInterface
$index_item = null;
foreach ($this->repos as $key => $repo)
{
- if (empty($repo->write_branch) || $repo->write_branch === 'default')
+ if (empty($repo->write_branch) || $repo->write_branch === 'default' || !$this->targetRepo($item, $repo))
{
continue;
}
+ $item = $this->mapItem($item);
+
+ $this->setRepoPlaceholders($repo);
+
$this->git->load_($repo->base ?? null, $repo->token ?? null);
if (($existing = $this->grep->get($item->guid, ['remote'], $repo)) !== null)
@@ -622,6 +611,43 @@ abstract class Set implements SetInterface
}
}
+ /**
+ * Set the Repo Placeholders
+ *
+ * @param object $repo The repo
+ *
+ * @return void
+ * @since 5.0.3
+ */
+ protected function setRepoPlaceholders(object $repo): void
+ {
+ $this->repoPlaceholders = $this->placeholders;
+ if (!empty($repo->placeholders) && is_array($repo->placeholders))
+ {
+ foreach ($repo->placeholders as $key => $value)
+ {
+ $this->repoPlaceholders[$key] = $value;
+ }
+ }
+ }
+
+ /**
+ * Update Placeholders in String
+ *
+ * @param string $string The value to update
+ *
+ * @return string
+ * @since 5.0.3
+ */
+ protected function updatePlaceholders(string $string): string
+ {
+ return str_replace(
+ array_keys($this->repoPlaceholders),
+ array_values($this->repoPlaceholders),
+ $string
+ );
+ }
+
/**
* Get index values
*
@@ -669,25 +695,36 @@ abstract class Set implements SetInterface
}
/**
- * Checks if two objects are equal by comparing their JSON representations.
+ * check that we have a target repo of this item
*
- * This method converts both input objects to JSON strings and compares these strings.
- * If the JSON strings are identical, the objects are considered equal.
+ * @param object $item The item
+ * @param object $repo The current repo
*
- * @param object $obj1 The first object to compare.
- * @param object $obj2 The second object to compare.
- *
- * @return bool True if the objects are equal, false otherwise.
- * @since 3.2.2
+ * @return bool
+ * @since 5.0.3
*/
- protected function areObjectsEqual(object $obj1, object $obj2): bool
+ protected function targetRepo(object $item, object $repo): bool
{
- // Convert both objects to JSON strings
- $json1 = json_encode($obj1);
- $json2 = json_encode($obj2);
+ return true; // for more control in children classes
+ }
- // Compare the JSON strings
- return $json1 === $json2;
+ /**
+ * Checks if two objects are equal by comparing their properties and values.
+ *
+ * This method converts both input objects to associative arrays, sorts the arrays by keys,
+ * and compares these sorted arrays.
+ *
+ * If the arrays are identical, the objects are considered equal.
+ *
+ * @param object|null $obj1 The first object to compare.
+ * @param object|null $obj2 The second object to compare.
+ *
+ * @return bool True if the objects are equal, false otherwise.
+ * @since 5.0.2
+ */
+ protected function areObjectsEqual(?object $obj1, ?object $obj2): bool
+ {
+ return ObjectHelper::equal($obj1, $obj2); // basic comparison
}
/**
diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Grep.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Grep.php
index d35f84b52..2b93cdd96 100644
--- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Grep.php
+++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Grep.php
@@ -143,6 +143,60 @@ final class Grep extends ExtendingGrep implements GrepInterface
// set the git details in params
$power->main_class_code = $code;
}
+
+ // set the git details in params
+ $path_guid = $path->guid ?? null;
+ if ($path_guid !== null)
+ {
+ // get the Settings meta
+ if (($meta = $this->contents->metadata($path->organisation, $path->repository, $path->index->{$guid}->settings, $branch)) !== null &&
+ isset($meta->sha))
+ {
+ if (isset($power->params) && is_object($power->params) &&
+ isset($power->params->source) && is_array($power->params->source))
+ {
+ $power->params->source[$path_guid . '-settings'] = $meta->sha;
+ }
+ else
+ {
+ $power->params = (object) [
+ 'source' => [$path_guid . '-settings' => $meta->sha]
+ ];
+ }
+ }
+ // get the power meta
+ if (($meta = $this->contents->metadata($path->organisation, $path->repository, $path->index->{$guid}->power, $branch)) !== null &&
+ isset($meta->sha))
+ {
+ if (isset($power->params) && is_object($power->params) &&
+ isset($power->params->source) && is_array($power->params->source))
+ {
+ $power->params->source[$path_guid . '-power'] = $meta->sha;
+ }
+ else
+ {
+ $power->params = (object) [
+ 'source' => [$path_guid . '-power' => $meta->sha]
+ ];
+ }
+ }
+ // get the README meta
+ if (($meta = $this->contents->metadata($path->organisation, $path->repository, $path->index->{$guid}->path . '/README.md', $branch)) !== null &&
+ isset($meta->sha))
+ {
+ if (isset($power->params) && is_object($power->params) &&
+ isset($power->params->source) && is_array($power->params->source))
+ {
+ $power->params->source[$path_guid . '-readme'] = $meta->sha;
+ }
+ else
+ {
+ $power->params = (object) [
+ 'source' => [$path_guid . '-readme' => $meta->sha]
+ ];
+ }
+ }
+ }
}
// reset back to the global base and token
diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Remote/Set.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Remote/Set.php
new file mode 100644
index 000000000..037a9a2a2
--- /dev/null
+++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Remote/Set.php
@@ -0,0 +1,794 @@
+
+ * @git 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\Power\Remote;
+
+
+use VDM\Joomla\Interfaces\GrepInterface as Grep;
+use VDM\Joomla\Interfaces\Data\ItemsInterface as Items;
+use VDM\Joomla\Interfaces\Readme\ItemInterface as ItemReadme;
+use VDM\Joomla\Interfaces\Readme\MainInterface as MainReadme;
+use VDM\Joomla\Interfaces\Git\Repository\ContentsInterface as Git;
+use VDM\Joomla\Componentbuilder\Power\Parser;
+use VDM\Joomla\Utilities\String\NamespaceHelper;
+use VDM\Joomla\Utilities\ArrayHelper;
+use VDM\Joomla\Utilities\StringHelper;
+use VDM\Joomla\Utilities\String\ClassfunctionHelper;
+use VDM\Joomla\Utilities\GuidHelper;
+use VDM\Joomla\Interfaces\Remote\SetInterface;
+use VDM\Joomla\Abstraction\Remote\Set as ExtendingSet;
+
+
+/**
+ * Set Power based on global unique ids to remote repository
+ *
+ * @since 5.0.2
+ */
+final class Set extends ExtendingSet implements SetInterface
+{
+ /**
+ * Table Name
+ *
+ * @var string
+ * @since 5.0.3
+ */
+ protected string $table = 'power';
+
+ /**
+ * Area Name
+ *
+ * @var string
+ * @since 5.0.3
+ */
+ protected string $area = 'Super Power';
+
+ /**
+ * Prefix Key
+ *
+ * @var string
+ * @since 5.0.3
+ */
+ protected string $prefix_key = 'Super---';
+
+ /**
+ * The item map
+ *
+ * @var array
+ * @since 5.0.3
+ */
+ protected array $map = [
+ 'add_head' => 'add_head',
+ 'description' => 'description',
+ 'extends' => 'extends',
+ 'extendsinterfaces' => 'extendsinterfaces',
+ 'guid' => 'guid',
+ 'head' => 'head',
+ 'use_selection' => 'use_selection',
+ 'implements' => 'implements',
+ 'load_selection' => 'load_selection',
+ 'name' => 'name',
+ 'power_version' => 'power_version',
+ 'system_name' => 'system_name',
+ 'type' => 'type',
+ 'namespace' => 'namespace',
+ 'composer' => 'composer',
+ 'add_licensing_template' => 'add_licensing_template',
+ 'licensing_template' => 'licensing_template',
+ 'main_class_code' => 'main_class_code'
+ ];
+
+ /**
+ * The index map
+ *
+ * @var array
+ * @since 5.0.3
+ */
+ protected array $index_map = [
+ 'name' => 'index_map_IndexName',
+ 'type' => 'index_map_TypeName',
+ 'namespace' => 'index_map_NameSpace',
+ 'code' => 'index_map_CodePath',
+ 'power' => 'index_map_PowerPath',
+ 'settings' => 'index_map_IndexSettingsPath',
+ 'path' => 'index_map_IndexPath',
+ 'spk' => 'index_map_IndexKey',
+ 'guid' => 'index_map_IndexGUID'
+ ];
+
+ /**
+ * The item settings file path
+ *
+ * @var string
+ * @since 5.0.3
+ */
+ protected string $settings_path = 'settings.json';
+
+ /**
+ * The index settings file path
+ *
+ * @var string
+ * @since 5.0.3
+ */
+ protected string $index_settings_path = 'super-powers.json';
+
+ /**
+ * The Parser Class.
+ *
+ * @var Parser|null
+ * @since 5.0.2
+ */
+ protected ?Parser $parser;
+
+ /**
+ * Constructor.
+ *
+ * @param array $repos The active repos
+ * @param Grep $grep The Grep Class.
+ * @param Items $items The Items Class.
+ * @param ItemReadme $itemReadme The Item Readme Class.
+ * @param MainReadme $mainReadme The Main Readme Class.
+ * @param Git $git The Contents Class.
+ * @param string|null $table The table name.
+ * @param string|null $settingsPath The settings path.
+ * @param string|null $settingsIndexPath The index settings path.
+ * @param Parser|null $parser The Parser Class.
+ *
+ * @since 3.2.2
+ */
+ public function __construct(array $repos, Grep $grep, Items $items,
+ ItemReadme $itemReadme, MainReadme $mainReadme, Git $git,
+ ?string $table = null, ?string $settingsPath = null,
+ ?string $settingsIndexPath = null, ?Parser $parser = null)
+ {
+ parent::__construct($repos, $grep, $items, $itemReadme, $mainReadme,
+ $git, $table, $settingsPath, $settingsIndexPath);
+
+ $this->parser = $parser;
+ }
+
+ /**
+ * Map a single item value (extends)
+ *
+ * @param object $item The item to be mapped
+ * @param array $item The bucket to to place new values
+ * @param string $map The item map to be mapped
+ *
+ * @return void
+ * @since 5.0.2
+ */
+ protected function mapItemValue_extends(object &$item, array &$power): void
+ {
+ if ($item->type !== 'interface')
+ {
+ $value = $item->extends ?? '';
+ $extends_custom = $item->extends_custom ?? null;
+ if ($value == -1 && $extends_custom !== null)
+ {
+ $power['extends_name'] = ClassfunctionHelper::safe(
+ $this->updatePlaceholders((string) $extends_custom)
+ );
+ $power['extends_custom'] = $extends_custom;
+ $power['extends'] = -1;
+ }
+ elseif (GuidHelper::valid($value))
+ {
+ $name = GuidHelper::item($value, 'power', 'a.name', 'componentbuilder');
+ if ($name !== null)
+ {
+ $power['extends_name'] = ClassfunctionHelper::safe(
+ $this->updatePlaceholders($name)
+ );
+ }
+ }
+ else
+ {
+ $power['extends'] = '';
+ }
+ // always rest these for normal classes
+ $power['extendsinterfaces'] = null;
+ $power['extendsinterfaces_custom'] = '';
+ }
+ }
+
+ /**
+ * Map a single item value (extendsinterfaces)
+ *
+ * @param object $item The item to be mapped
+ * @param array $item The bucket to to place new values
+ * @param string $map The item map to be mapped
+ *
+ * @return void
+ * @since 5.0.2
+ */
+ protected function mapItemValue_extendsinterfaces(object &$item, array &$power): void
+ {
+ if ($item->type === 'interface')
+ {
+ $values = $item->extendsinterfaces ?? null;
+ if (!empty($values))
+ {
+ $values = (array) $values;
+ $extends_names = [];
+ $extendsinterfaces_custom = $item->extendsinterfaces_custom ?? null;
+
+ foreach ($values as $value)
+ {
+ if ($value == -1 && StringHelper::check($extendsinterfaces_custom))
+ {
+ $extends_names[] = ClassfunctionHelper::safe(
+ $this->updatePlaceholders($extendsinterfaces_custom)
+ );
+
+ $power['extendsinterfaces_custom'] = $extendsinterfaces_custom;
+ $extendsinterfaces_custom = null;
+ }
+ elseif (GuidHelper::valid($value))
+ {
+ $name = GuidHelper::item($value, 'power', 'a.name', 'componentbuilder');
+ if ($name !== null)
+ {
+ $extends_names[] = ClassfunctionHelper::safe(
+ $this->updatePlaceholders($name)
+ );
+ }
+ }
+ }
+
+ if ($extends_names !== [])
+ {
+ $power['extendsinterfaces'] = array_values($values);
+ $power['extends_name'] = implode(', ', $extends_names);
+ }
+ }
+ else
+ {
+ $power['extendsinterfaces'] = null;
+ $power['extendsinterfaces_custom'] = '';
+ }
+ // always rest these for interfaces
+ $power['extends'] = '';
+ $power['extends_custom'] = '';
+ }
+ }
+
+ /**
+ * Map a single item value (use_selection)
+ *
+ * @param object $item The item to be mapped
+ * @param array $item The bucket to to place new values
+ * @param string $map The item map to be mapped
+ *
+ * @return void
+ * @since 5.0.2
+ */
+ protected function mapItemValue_use_selection(object &$item, array &$power): void
+ {
+ $value = $item->use_selection ?? null;
+ if (!empty($value))
+ {
+ $value = (array) $value;
+ $power['use_selection'] = $value;
+ }
+ else
+ {
+ $power['use_selection'] = null;
+ }
+ }
+
+ /**
+ * Map a single item value (load_selection)
+ *
+ * @param object $item The item to be mapped
+ * @param array $item The bucket to to place new values
+ * @param string $map The item map to be mapped
+ *
+ * @return void
+ * @since 5.0.2
+ */
+ protected function mapItemValue_load_selection(object &$item, array &$power): void
+ {
+ $value = $item->load_selection ?? null;
+ if (!empty($value))
+ {
+ $value = (array) $value;
+ $power['load_selection'] = $value;
+ }
+ else
+ {
+ $power['load_selection'] = null;
+ }
+ }
+
+ /**
+ * Map a single item value (composer)
+ *
+ * @param object $item The item to be mapped
+ * @param array $item The bucket to to place new values
+ * @param string $map The item map to be mapped
+ *
+ * @return void
+ * @since 5.0.2
+ */
+ protected function mapItemValue_composer(object &$item, array &$power): void
+ {
+ $value = $item->composer ?? null;
+ if (!empty($value))
+ {
+ $value = (array) $value;
+ $power['composer'] = array_values($value);
+ }
+ else
+ {
+ $power['composer'] = '';
+ }
+ }
+
+ /**
+ * Map a single item value (implements)
+ *
+ * @param object $item The item to be mapped
+ * @param array $item The bucket to to place new values
+ * @param string $map The item map to be mapped
+ *
+ * @return void
+ * @since 5.0.2
+ */
+ protected function mapItemValue_implements(object &$item, array &$power): void
+ {
+ $values = $item->implements ?? '';
+ if (!empty($values))
+ {
+ $values = (array) $values;
+ $implement_names = [];
+ $implements_custom = $item->implements_custom ?? null;
+
+ foreach ($values as $value)
+ {
+ if ($value == -1 && StringHelper::check($implements_custom))
+ {
+ $implement_names[] = ClassfunctionHelper::safe(
+ $this->updatePlaceholders($implements_custom)
+ );
+ $implements_custom = null;
+ }
+ elseif (GuidHelper::valid($value))
+ {
+ $name = GuidHelper::item($value, 'power', 'a.name', 'componentbuilder');
+ if ($name !== null)
+ {
+ $implement_names[] = ClassfunctionHelper::safe(
+ $this->updatePlaceholders($name)
+ );
+ }
+ }
+ }
+
+ if ($implement_names !== [])
+ {
+ $power['implements'] = array_values($values);
+ $power['implement_names'] = $implement_names;
+ }
+ else
+ {
+ $power['implements'] = null;
+ }
+ }
+ }
+
+ /**
+ * update an existing item (if changed)
+ *
+ * @param object $item
+ * @param object $existing
+ * @param object $repo
+ *
+ * @return bool
+ * @since 5.0.3
+ */
+ protected function updateItem(object $item, object $existing, object $repo): bool
+ {
+ // make sure there was a change
+ $sha = $existing->params->source[$repo->guid . '-settings'] ?? null;
+ $_existing = $this->mapItem($existing);
+
+ if ($sha === null || $this->areObjectsEqual($item, $_existing))
+ {
+ return false;
+ }
+ else
+ {
+ // strip these values form the settings
+ $code = (string) $item->main_class_code ?? '';
+ $extends_name = (string) $item->extends_name ?? '';
+ $implement_names = (string) $item->implement_names ?? '';
+ unset($item->main_class_code);
+ unset($item->extends_name);
+ unset($item->implement_names);
+
+ $this->git->update(
+ $repo->organisation, // The owner name.
+ $repo->repository, // The repository name.
+ 'src/' . $item->guid . '/' . $this->getSettingsPath(), // The file path.
+ json_encode($item, JSON_PRETTY_PRINT), // The file content.
+ 'Update ' . $item->system_name . ' settings', // The commit message.
+ $sha, // The blob SHA of the old file.
+ $repo->write_branch // The branch name.
+ );
+
+ $item->main_class_code = $code;
+ $item->extends_name = $extends_name;
+ $item->implement_names = $implement_names;
+ }
+
+ return $this->updatePower($item, $existing, $repo);
+ }
+
+ /**
+ * update an existing power code (if changed)
+ *
+ * @param object $item
+ * @param object $existing
+ * @param object $repo
+ *
+ * @return bool
+ * @since 5.0.3
+ */
+ protected function updatePower(object $item, object $existing, object $repo): bool
+ {
+ // make sure there was a change
+ $sha = $existing->params->source[$repo->guid . '-power'] ?? null;
+
+ if ($sha === null)
+ {
+ return false;
+ }
+
+ // Calculate the new SHA from the current content
+ $power = $item->main_class_code ?? '';
+ $newSha = sha1("blob " . strlen($power) . "\0" . $power);
+
+ // Check if the new SHA matches the existing SHA
+ if ($sha === $newSha)
+ {
+ return false;
+ }
+
+ $this->git->update(
+ $repo->organisation, // The owner name.
+ $repo->repository, // The repository name.
+ 'src/' . $item->guid . '/code.power', // The file path.
+ $power, // The file content.
+ 'Update ' . $item->system_name . ' code', // The commit message.
+ $sha, // The blob SHA of the old file.
+ $repo->write_branch // The branch name.
+ );
+
+ return true;
+ }
+
+ /**
+ * create a new item
+ *
+ * @param object $item
+ * @param object $repo
+ *
+ * @return void
+ * @since 5.0.3
+ */
+ protected function createItem(object $item, object $repo): void
+ {
+ // strip these values form the settings
+ $code = (string) $item->main_class_code ?? '';
+ $extends_name = (string) $item->extends_name ?? '';
+ $implement_names = (string) $item->implement_names ?? '';
+ unset($item->main_class_code);
+ unset($item->extends_name);
+ unset($item->implement_names);
+
+ $this->git->create(
+ $repo->organisation, // The owner name.
+ $repo->repository, // The repository name.
+ 'src/' . $item->guid . '/' . $this->getSettingsPath(), // The file path.
+ json_encode($item, JSON_PRETTY_PRINT), // The file content.
+ 'Create ' . $item->system_name . ' settings', // The commit message.
+ $repo->write_branch // The branch name.
+ );
+
+ $item->main_class_code = $code;
+ $item->extends_name = $extends_name;
+ $item->implement_names = $implement_names;
+
+ $this->createPower($item, $repo);
+ }
+
+ /**
+ * create a new power
+ *
+ * @param object $item
+ * @param object $repo
+ *
+ * @return void
+ * @since 5.0.3
+ */
+ protected function createPower(object $item, object $repo): void
+ {
+ $this->git->create(
+ $repo->organisation, // The owner name.
+ $repo->repository, // The repository name.
+ 'src/' . $item->guid . '/code.power', // The file path.
+ $item->main_class_code, // The file content.
+ 'Create ' . $item->system_name . ' code', // The commit message.
+ $repo->write_branch // The branch name.
+ );
+ }
+
+ /**
+ * update an existing item readme
+ *
+ * @param object $item
+ * @param object $existing
+ * @param object $repo
+ *
+ * @return void
+ * @since 5.0.3
+ */
+ protected function updateItemReadme(object $item, object $existing, object $repo): void
+ {
+ // make sure there was a change
+ $sha = $existing->params->source[$repo->guid . '-readme'] ?? null;
+ if ($sha === null)
+ {
+ return;
+ }
+
+ if ($this->parser !== null)
+ {
+ $item->parsed_class_code = $this->parser->code($item->main_class_code);
+ }
+ $item->code_name = $this->index_map_IndexName($item);
+ $item->_namespace = $this->index_map_NameSpace($item);
+
+ $readme = $this->itemReadme->get($item);
+ $newSha = sha1("blob " . strlen($readme) . "\0" . $readme);
+
+ // Check if the new SHA matches the existing SHA
+ if ($sha === $newSha)
+ {
+ return;
+ }
+
+ $this->git->update(
+ $repo->organisation, // The owner name.
+ $repo->repository, // The repository name.
+ 'src/' . $item->guid . '/README.md', // The file path.
+ $readme, // The file content.
+ 'Update ' . $item->system_name . ' readme file', // The commit message.
+ $sha, // The blob SHA of the old file.
+ $repo->write_branch // The branch name.
+ );
+ }
+
+ /**
+ * create a new item readme
+ *
+ * @param object $item
+ * @param object $repo
+ *
+ * @return void
+ * @since 5.0.3
+ */
+ protected function createItemReadme(object $item, object $repo): void
+ {
+ if ($this->parser !== null)
+ {
+ $item->parsed_class_code = $this->parser->code($item->main_class_code);
+ }
+ $item->code_name = $this->index_map_IndexName($item);
+ $item->_namespace = $this->index_map_NameSpace($item);
+
+ $this->git->create(
+ $repo->organisation, // The owner name.
+ $repo->repository, // The repository name.
+ 'src/' . $item->guid . '/README.md', // The file path.
+ $this->itemReadme->get($item), // The file content.
+ 'Create ' . $item->system_name . ' readme file', // The commit message.
+ $repo->write_branch // The branch name.
+ );
+ }
+
+ /**
+ * check that we have a target repo of this item
+ *
+ * @param object $item The item
+ * @param object $repo The current repo
+ *
+ * @return bool
+ * @since 5.0.3
+ */
+ protected function targetRepo(object $item, object $repo): bool
+ {
+ if (!isset($item->approved) || $item->approved != 1 ||
+ !isset($item->approved_paths) || !is_array($item->approved_paths))
+ {
+ return false;
+ }
+
+ $repo_path = "{$repo->organisation}/{$repo->repository}";
+
+ foreach ($item->approved_paths as $approved_path)
+ {
+ if ($repo_path === $approved_path)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the item name for the index values
+ *
+ * @param object $item
+ *
+ * @return string|null
+ * @since 5.0.3
+ */
+ protected function index_map_IndexName(object $item): ?string
+ {
+ $name = $item->name ?? null;
+ if ($name !== null)
+ {
+ return ClassfunctionHelper::safe(
+ $this->updatePlaceholders($name)
+ );
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the item type for the index values
+ *
+ * @param object $item
+ *
+ * @return string|null
+ * @since 5.0.3
+ */
+ protected function index_map_TypeName(object $item): ?string
+ {
+ return $item->type ?? null;
+ }
+
+ /**
+ * Get the item code path for the index values
+ *
+ * @param object $item
+ *
+ * @return string|null
+ * @since 5.0.3
+ */
+ protected function index_map_CodePath(object $item): ?string
+ {
+ return $this->index_map_IndexPath($item) . '/code.php';
+ }
+
+ /**
+ * Get the item power path for the index values
+ *
+ * @param object $item
+ *
+ * @return string|null
+ * @since 5.0.3
+ */
+ protected function index_map_PowerPath(object $item): ?string
+ {
+ return $this->index_map_IndexPath($item) . '/code.power';
+ }
+
+ /**
+ * Get the item namespace for the index values
+ *
+ * @param object $item
+ *
+ * @return string|null
+ * @since 5.0.3
+ */
+ protected function index_map_NameSpace(object $item): ?string
+ {
+ return $this->getNamespace($item->namespace ?? '', $item->name ?? '');
+ }
+
+ /**
+ * Set the namespace for this power
+ *
+ * @param string $namespace The raw namespace
+ * @param string $className The class name
+ *
+ * @return string|null
+ * @since 5.0.3
+ */
+ protected function getNamespace(string $namespace, string $className): ?string
+ {
+ // set namespace
+ $namespace = $this->updatePlaceholders($namespace);
+
+ // validate namespace
+ if (strpos($namespace, '\\') === false)
+ {
+ // we break out here
+ return null;
+ }
+
+ // setup the path array
+ $path_array = (array) explode('\\', $namespace);
+
+ // make sure it has two or more
+ if (ArrayHelper::check($path_array) <= 1)
+ {
+ // we break out here
+ return null;
+ }
+
+ // get the file and class name (the last value in array)
+ $file_name = array_pop($path_array);
+
+ // do we have src folders
+ if (strpos($file_name, '.') !== false)
+ {
+ // we have src folders in the namespace
+ $src_array = (array) explode('.', $file_name);
+
+ // get the file and class name (the last value in array)
+ $file_name = array_pop($src_array);
+
+ // namespace array
+ $namespace_array = [...$path_array, ...$src_array];
+ }
+ else
+ {
+ // namespace array
+ $namespace_array = $path_array;
+ }
+
+ // the last value is the same as the class name
+ if ($file_name !== $className)
+ {
+ // we break out here
+ return null;
+ }
+
+ // make sure the arrays are namespace safe
+ $namespace_array =
+ array_map(
+ fn($val) => $this->getCleanNamespace($val),
+ $namespace_array
+ );
+
+ // set the actual class namespace
+ return implode('\\', $namespace_array);
+ }
+
+ /**
+ * Get Clean Namespace without use or ; as part of the name space
+ *
+ * @param string $namespace The actual name space
+ *
+ * @return string
+ * @since 5.0.3
+ */
+ protected function getCleanNamespace(string $namespace): string
+ {
+ // trim possible (use) or (;) or (starting or ending \) added to the namespace
+ return NamespaceHelper::safe(str_replace(['use ', ';'], '', $namespace));
+ }
+}
+
diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Service/Power.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Service/Power.php
index 34af01e4a..2763c2ff3 100644
--- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Service/Power.php
+++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Power/Service/Power.php
@@ -18,7 +18,11 @@ use VDM\Joomla\Componentbuilder\Power\Config;
use VDM\Joomla\Componentbuilder\Table;
use VDM\Joomla\Componentbuilder\Power\Grep;
use VDM\Joomla\Componentbuilder\Power\Remote\Get;
+use VDM\Joomla\Componentbuilder\Power\Remote\Set;
use VDM\Joomla\Componentbuilder\Power\Parser;
+use VDM\Joomla\Componentbuilder\Power\Plantuml;
+use VDM\Joomla\Componentbuilder\Power\Readme\Item as ItemReadme;
+use VDM\Joomla\Componentbuilder\Power\Readme\Main as MainReadme;
/**
@@ -50,8 +54,20 @@ class Power implements ServiceProviderInterface
$container->alias(Get::class, 'Power.Remote.Get')
->share('Power.Remote.Get', [$this, 'getRemoteGet'], true);
+ $container->alias(Set::class, 'Power.Remote.Set')
+ ->share('Power.Remote.Set', [$this, 'getRemoteSet'], true);
+
$container->alias(Parser::class, 'Power.Parser')
->share('Power.Parser', [$this, 'getParser'], true);
+
+ $container->alias(Plantuml::class, 'Power.Plantuml')
+ ->share('Power.Plantuml', [$this, 'getPlantuml'], true);
+
+ $container->alias(ItemReadme::class, 'Power.Readme.Item')
+ ->share('Power.Readme.Item', [$this, 'getItemReadme'], true);
+
+ $container->alias(MainReadme::class, 'Power.Readme.Main')
+ ->share('Power.Readme.Main', [$this, 'getMainReadme'], true);
}
/**
@@ -113,6 +129,55 @@ class Power implements ServiceProviderInterface
);
}
+ /**
+ * Get The Remote Set Class.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Set
+ * @since 5.0.3
+ */
+ public function getRemoteSet(Container $container): Set
+ {
+ return new Set(
+ $container->get('Config')->approved_paths,
+ $container->get('Power.Grep'),
+ $container->get('Data.Items'),
+ $container->get('Power.Readme.Item'),
+ $container->get('Power.Readme.Main'),
+ $container->get('Gitea.Repository.Contents'), null, null, null,
+ $container->get('Power.Parser')
+ );
+ }
+
+ /**
+ * Get The Readme Class.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return ItemReadme
+ * @since 5.0.3
+ */
+ public function getItemReadme(Container $container): ItemReadme
+ {
+ return new ItemReadme(
+ $container->get('Power.Plantuml')
+ );
+ }
+
+ /**
+ * Get The Readme Class.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return MainReadme
+ * @since 5.0.3
+ */
+ public function getMainReadme(Container $container): MainReadme
+ {
+ return new MainReadme();
+ }
+
/**
* Get The Parser Class.
*
@@ -124,6 +189,19 @@ class Power implements ServiceProviderInterface
public function getParser(Container $container): Parser
{
return new Parser();
+ }
+
+ /**
+ * Get The Plantuml Class.
+ *
+ * @param Container $container The DI container.
+ *
+ * @return Plantuml
+ * @since 5.0.3
+ */
+ public function getPlantuml(Container $container): Plantuml
+ {
+ return new Plantuml();
}
}
diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table.php
index 6c228d771..2f23b7700 100644
--- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table.php
+++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Table.php
@@ -9104,6 +9104,22 @@ final class Table extends BaseTable implements Tableinterface
'key' => true,
],
],
+ 'addplaceholders' => [
+ 'name' => 'addplaceholders',
+ 'label' => 'COM_COMPONENTBUILDER_REPOSITORY_ADDPLACEHOLDERS_LABEL',
+ 'type' => 'subform',
+ 'title' => false,
+ 'list' => 'repositories',
+ 'store' => 'json',
+ 'tab_name' => 'Placeholders',
+ 'db' => [
+ 'type' => 'TEXT',
+ 'default' => 'EMPTY',
+ 'null_switch' => 'NOT NULL',
+ 'unique_key' => false,
+ 'key' => false,
+ ],
+ ],
'access_repo' => [
'name' => 'access_repo',
'label' => 'COM_COMPONENTBUILDER_REPOSITORY_ACCESS_REPO_LABEL',
diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Utilities/RepoHelper.php b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Utilities/RepoHelper.php
index 52036a9e7..0fca27e52 100644
--- a/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Utilities/RepoHelper.php
+++ b/libraries/vendor_jcb/VDM.Joomla/src/Componentbuilder/Utilities/RepoHelper.php
@@ -13,6 +13,8 @@ namespace VDM\Joomla\Componentbuilder\Utilities;
use Joomla\CMS\Factory;
+use VDM\Joomla\Utilities\JsonHelper;
+use VDM\Joomla\Utilities\ArrayHelper;
/**
@@ -45,6 +47,7 @@ abstract class RepoHelper
'username',
'target',
'access_repo',
+ 'addplaceholders',
'guid'
)))
->from($db->quoteName('#__componentbuilder_repository'))
@@ -66,13 +69,43 @@ abstract class RepoHelper
unset($item->token);
}
unset($item->access_repo);
+
+ $item->placeholders = self::setPlaceholders($item->addplaceholders ?? '');
+ unset($item->addplaceholders);
+
$path = $item->organisation . '/' . $item->repository;
$options[$path] = $item;
}
+
return $options;
}
return null;
+ }
+
+ /**
+ * set the placeholders for this repo
+ *
+ * @param string $placeholders The repo placeholders
+ *
+ * @return array The result set
+ * @since 5.0.3
+ **/
+ protected static function setPlaceholders(string $placeholders): array
+ {
+ $bucket = [];
+ if (JsonHelper::check($placeholders))
+ {
+ $placeholders = json_decode((string) $placeholders, true);
+ if (ArrayHelper::check($placeholders))
+ {
+ foreach ($placeholders as $row)
+ {
+ $bucket[$row['target']] = $row['value'];
+ }
+ }
+ }
+ return $bucket;
}
}
diff --git a/libraries/vendor_jcb/VDM.Joomla/src/Utilities/ObjectHelper.php b/libraries/vendor_jcb/VDM.Joomla/src/Utilities/ObjectHelper.php
index 2646f7c37..499035624 100644
--- a/libraries/vendor_jcb/VDM.Joomla/src/Utilities/ObjectHelper.php
+++ b/libraries/vendor_jcb/VDM.Joomla/src/Utilities/ObjectHelper.php
@@ -39,17 +39,19 @@ abstract class ObjectHelper
}
/**
- * Compare two objects for equality based on their property values.
+ * Checks if two objects are equal by comparing their properties and values.
*
- * Note that this method works only for simple objects that don't
- * contain any nested objects or resource references. If you need
- * to compare more complex objects, you may need to use a
- * more advanced method such as serialization or reflection.
+ * This method converts both input objects to
+ * associative arrays, sorts the arrays by keys,
+ * and compares these sorted arrays.
*
- * @param object|null $obj1 The first object to compare.
- * @param object|null $obj2 The second object to compare.
+ * If the arrays are identical, the objects are considered equal.
*
- * @return bool True if the objects have the same key-value pairs and false otherwise.
+ * @param object|null $obj1 The first object to compare.
+ * @param object|null $obj2 The second object to compare.
+ *
+ * @return bool True if the objects are equal, false otherwise.
+ * @since 5.0.2
*/
public static function equal(?object $obj1, ?object $obj2): bool
{
@@ -62,17 +64,41 @@ abstract class ObjectHelper
return false;
}
- // Convert the objects to arrays of their property values using get_object_vars.
- $array1 = get_object_vars($obj1);
- $array2 = get_object_vars($obj2);
+ // Convert both objects to associative arrays
+ $array1 = json_decode(json_encode($obj1), true);
+ $array2 = json_decode(json_encode($obj2), true);
- // Compare the arrays using array_diff_assoc to detect any differences.
- $diff1 = array_diff_assoc($array1, $array2);
- $diff2 = array_diff_assoc($array2, $array1);
+ // Sort the arrays by keys
+ self::recursiveKsort($array1);
+ self::recursiveKsort($array2);
- // If the arrays have the same key-value pairs, they will have no differences, so return true.
- return empty($diff1) && empty($diff2);
+ // Compare the sorted arrays
+ return $array1 === $array2;
}
-
+
+ /**
+ * Recursively sorts an associative array by keys.
+ *
+ * This method will sort an associative array by its keys at all levels.
+ *
+ * @param array &$array The array to sort.
+ *
+ * @return void
+ * @since 5.0.2
+ */
+ protected static function recursiveKsort(array &$array): void
+ {
+ // Sort the array by its keys
+ ksort($array);
+
+ // Recursively sort nested arrays
+ foreach ($array as &$value)
+ {
+ if (is_array($value))
+ {
+ self::recursiveKsort($value);
+ }
+ }
+ }
}