Release of v5.1.1-beta3

Fixes issue with loading the Component Builder Wiki. Adds advanced version update notice to the Component Builder Dashboard. Completely refactors the class that builds the Component Dashboard. #1134.
This commit is contained in:
2025-06-23 17:02:17 +00:00
parent 3c1057a830
commit dbebb5663c
28 changed files with 2067 additions and 888 deletions

View File

@@ -44,21 +44,25 @@ final class PermissionDashboard extends Registry implements Registryinterface
use VarExport;
/**
* Get the build permission dashboard code
* Get the build permission dashboard code.
*
* @return string
* @since 3.2.0
* @return string
* @since 3.2.0
* @since 5.1.1 Changed to class property.
*/
public function build(): string
{
if ($this->isActive())
{
return PHP_EOL . Indent::_(2) . "//" . Line::_(__Line__, __Class__)
. " view access array" . PHP_EOL . Indent::_(2)
. "\$viewAccess = " . $this->varExport() . ';';
}
$indent = Indent::_(1);
$docBlock = PHP_EOL . $indent . '/**'
. PHP_EOL . $indent . ' *' . Line::_(__LINE__, __CLASS__) . ' View access array.'
. PHP_EOL . $indent . ' *'
. PHP_EOL . $indent . ' * @var array<string, string>'
. PHP_EOL . $indent . ' * @since 5.1.1'
. PHP_EOL . $indent . ' */';
return '';
$value = $this->isActive() ? $this->varExport(null, 1) : '[]';
return $docBlock . PHP_EOL . $indent . 'protected array $viewAccess = ' . $value . ';' . PHP_EOL;
}
}

View File

@@ -15,6 +15,8 @@ namespace VDM\Joomla\Componentbuilder\Power\Service;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use VDM\Joomla\Github\Repository\Contents;
use VDM\Joomla\Github\Repository\Tags;
use VDM\Joomla\Github\Repository\Wiki;
/**
@@ -36,6 +38,12 @@ class Github implements ServiceProviderInterface
{
$container->alias(Contents::class, 'Github.Repository.Contents')
->share('Github.Repository.Contents', [$this, 'getContents'], true);
$container->alias(Tags::class, 'Github.Repository.Tags')
->share('Github.Repository.Tags', [$this, 'getTags'], true);
$container->alias(Wiki::class, 'Github.Repository.Wiki')
->share('Github.Repository.Wiki', [$this, 'getWiki'], true);
}
/**
@@ -53,6 +61,40 @@ class Github implements ServiceProviderInterface
$container->get('Github.Utilities.Uri'),
$container->get('Github.Utilities.Response')
);
}
/**
* Get the Tags class
*
* @param Container $container The DI container.
*
* @return Tags
* @since 5.1.1
*/
public function getTags(Container $container): Tags
{
return new Tags(
$container->get('Github.Utilities.Http'),
$container->get('Github.Utilities.Uri'),
$container->get('Github.Utilities.Response')
);
}
/**
* Get the Wiki class
*
* @param Container $container The DI container.
*
* @return Wiki
* @since 5.1.1
*/
public function getWiki(Container $container): Wiki
{
return new Wiki(
$container->get('Github.Utilities.Http'),
$container->get('Github.Utilities.Uri'),
$container->get('Github.Utilities.Response')
);
}
}

View File

@@ -0,0 +1,386 @@
<?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\Remote;
use Joomla\CMS\Language\Text;
use VDM\Joomla\Github\Factory as Github;
use VDM\Joomla\Gitea\Factory as Gitea;
use VDM\Component\Componentbuilder\Administrator\Helper\ComponentbuilderHelper;
/**
* Get Remote Version
*
* @since 5.1.1
*/
final class Version
{
/**
* GitHub organization name.
*
* @var string
* @since 5.1.1
*/
protected string $githubOrg;
/**
* GitHub repository name.
*
* @var string
* @since 5.1.1
*/
protected string $githubRepo;
/**
* Gitea organization name.
*
* @var string
* @since 5.1.1
*/
protected string $giteaOrg;
/**
* Gitea repository name.
*
* @var string
* @since 5.1.1
*/
protected string $giteaRepo;
/**
* Constructor to set repository organization and name for both GitHub and Gitea.
*
* @param string $githubOrg GitHub organization name.
* @param string $githubRepo GitHub repository name.
* @param string $giteaOrg Gitea organization name.
* @param string $giteaRepo Gitea repository name.
*
* @since 5.1.1
*/
public function __construct(string $githubOrg, string $githubRepo, string $giteaOrg, string $giteaRepo)
{
$this->githubOrg = $githubOrg;
$this->githubRepo = $githubRepo;
$this->giteaOrg = $giteaOrg;
$this->giteaRepo = $giteaRepo;
}
/**
* Get the current version notice.
*
* Compares the installed version of the component with the latest available
* version from the repository tags and returns an appropriate message.
*
* @param string|null $version Optional version to compare if manifest version not found.
*
* @return array The array with 'notice' or 'error' and optional 'github-error' / 'gitea-error'.
* @since 5.1.1
*/
public function get(?string $version = null): array
{
$defaultDownloadLink = 'https://git.vdm.dev/joomla/pkg-component-builder/releases';
$manifest = ComponentbuilderHelper::manifest();
$localVersion = (string) ($manifest->version ?? $version ?? '1.0.0');
$major = explode('.', $localVersion)[0] ?? '1';
$errors = [
'error' => Text::sprintf(
'There was an error getting the %sVersion details</a>.',
'<a href="' . $defaultDownloadLink . '" title="Version Tag details">'
)
];
$tags = $this->getRepositoryTags($errors);
if (empty($tags) || !isset($tags[0]->name))
{
return $this->mergeErrors($errors);
}
$grouped = $this->groupTagsByType($tags, $major);
$latestStableTag = $grouped['stable'][0] ?? null;
$latestStableVer = $latestStableTag ? ltrim($latestStableTag->name, 'vV') : '1.0.0';
$latestStableLink = $latestStableTag->zipball_url ?? $defaultDownloadLink;
$versionType = $this->getVersionType($localVersion);
$versionCompare = version_compare($localVersion, $latestStableVer);
// Handle pre-release versions
if ($versionType !== 'stable')
{
$isLatestPre = $this->isLatestPreRelease($localVersion, $grouped['pre']);
$state = $isLatestPre ? Text::_('COM_COMPONENTBUILDER_PRE_RELEASE') : Text::_('COM_COMPONENTBUILDER_OUT_OF_DATE');
$statement = sprintf(
'<small><span class="icon-wrench"></span>&nbsp;%s</small>',
$state
);
$color = $isLatestPre ? '#F7B033' : 'red';
$state = $isLatestPre
? Text::sprintf("COM_COMPONENTBUILDER_THANK_YOU_FOR_TESTING_THE_S_RELEASE_YOURE_A_JCB_HERO", strtoupper($versionType))
: Text::sprintf("COM_COMPONENTBUILDER_GRAB_THE_LATEST_S_TESTING_RELEASE", strtoupper($versionType));
$notice = sprintf(
' <a style="color:%s;" href="%s" title="%s">%s</a>',
$color, $grouped['pre'][0]->zipball_url ?? $defaultDownloadLink, $state, $statement
);
return ['notice' => $notice];
}
// Stable and up-to-date
if ($versionCompare === 0)
{
$notice = sprintf(
'<small><span class="icon-shield"></span>&nbsp;%s</small>',
Text::_('COM_COMPONENTBUILDER_UP_TO_DATE')
);
if (!empty($grouped['pre']))
{
$notice = sprintf(
' <a style="color:green;" href="%s" title="%s">%s</a>',
$grouped['pre'][0]->zipball_url ?? $defaultDownloadLink,
Text::_('COM_COMPONENTBUILDER_HELP_US_TEST_THE_UPCOMING_RELEASE'),
$notice
);
}
return ['notice' => $notice];
}
// Stable but outdated
return ['notice' => sprintf(
'<small><span style="color:red;"><span class="icon-warning-circle"></span>&nbsp;%s</span> ' .
'<a style="color:green;" href="%s" title="%s">%s</a></small>',
Text::_('COM_COMPONENTBUILDER_OUT_OF_DATE') . '!',
$latestStableLink,
Text::_('COM_COMPONENTBUILDER_YOU_CAN_DIRECTLY_DOWNLOAD_THE_LATEST_UPDATE_OR_USE_THE_JOOMLA_UPDATE_AREA'),
Text::_('COM_COMPONENTBUILDER_DOWNLOAD_UPDATE') . '!'
)];
}
/**
* Check if the local pre-release version is the latest.
*
* @param string $localVersion The current local version.
* @param array<int, object> $preTags All matching pre-release tags.
*
* @return bool
* @since 5.1.1
*/
protected function isLatestPreRelease(string $localVersion, array $preTags): bool
{
if (empty($preTags))
{
return false;
}
usort($preTags, static fn($a, $b) => version_compare($b->name, $a->name));
$latestPre = ltrim($preTags[0]->name, 'vV');
return version_compare($localVersion, $latestPre) === 0;
}
/**
* Fetch tags from GitHub or fallback to various Gitea instances.
*
* Appends source keys to the return array on failure.
*
* @param array &$errors The response array to populate error messages into.
*
* @return array<int, object> List of tags or an empty array.
* @since 5.1.1
*/
protected function getRepositoryTags(array &$errors): array
{
// Attempt GitHub fetch
if ($tags = $this->tryFetchTags('github', $errors))
{
return $tags;
}
// Try default VDM Gitea instance
if ($tags = $this->tryFetchTags('gitea', $errors))
{
return $tags;
}
// Try alternative Gitea hosts
$alternatives = [
'codeberg' => 'https://codeberg.org',
'tildegit' => 'https://tildegit.org',
'disroot' => 'https://git.disroot.org',
];
foreach ($alternatives as $key => $url)
{
Gitea::_('Gitea.Repository.Tags')->load_($url, '');
if ($tags = $this->tryFetchTags($key, $errors))
{
return $tags;
}
}
// Final reset to default host
return [];
}
/**
* Attempt to fetch tags from a given source and record any error.
*
* @param string $source One of: 'github', 'gitea', 'codeberg', 'tildegit', 'disroot'.
* @param array &$errors Reference to return array for error recording.
*
* @return array<int, object>|null List of tags or null on failure.
* @since 5.1.1
*/
protected function tryFetchTags(string $source, array &$errors): ?array
{
try {
return match ($source) {
'github' => Github::_('Github.Repository.Tags')->list($this->githubOrg, $this->githubRepo),
default => Gitea::_('Gitea.Repository.Tags')->list($this->giteaOrg, $this->giteaRepo),
};
} catch (\Throwable $e) {
$errors["{$source}-error"] = $e->getMessage();
} finally {
if ($source !== 'github' && $source !== 'gitea') {
Gitea::_('Gitea.Repository.Tags')->reset_();
}
}
return null;
}
/**
* Groups and sorts tags into stable and pre-release arrays.
*
* @param array<int, object> $tags List of repository tags.
* @param string $major Major version prefix to match.
*
* @return array{stable: array<int, object>, pre: array<int, object>}
* @since 5.1.1
*/
protected function groupTagsByType(array $tags, string $major): array
{
$stable = [];
$pre = [];
foreach ($tags as $tag)
{
if (!isset($tag->name) || strpos($tag->name, 'v' . $major) !== 0)
{
continue;
}
$version = strtolower($tag->name);
if (preg_match('/-(alpha|beta|rc)\d*/', $version)) {
$pre[] = $tag;
} else {
$stable[] = $tag;
}
}
usort($stable, static fn($a, $b) => version_compare($b->name, $a->name));
usort($pre, static fn($a, $b) => version_compare($b->name, $a->name));
return ['stable' => $stable, 'pre' => $pre];
}
/**
* Determine the version type from the string.
*
* @param string $version The version string to analyze.
*
* @return string 'stable', 'alpha', 'beta' or 'rc'.
* @since 5.1.1
*/
protected function getVersionType(string $version): string
{
$version = strtolower($version);
if (str_contains($version, '-alpha'))
{
return 'alpha';
}
if (str_contains($version, '-beta'))
{
return 'beta';
}
if (str_contains($version, '-rc'))
{
return 'rc';
}
return 'stable';
}
/**
* Merge repository fetch errors into the error message as a toggleable Bootstrap block.
*
* This enhances the 'error' message by appending a red icon and a collapsible
* section with the actual GitHub/Gitea error messages.
*
* @param array $errors The array containing at least 'error' and optionally error details.
*
* @return array The modified array with a richer 'error' message.
* @since 5.1.1
*/
protected function mergeErrors(array $errors): array
{
$bucket = [];
foreach ($errors as $key => $value)
{
if ($key !== 'error')
{
$source = ucfirst(explode('-', $key)[0]);
$bucket[] = '<li><strong>' . $source . ':</strong> ' . htmlspecialchars($value) . '</li>';
}
}
if (empty($bucket))
{
return $errors;
}
$uid = uniqid('version-error-');
$toggle = sprintf(
'&nbsp;<a href="#" class="text-danger ms-2" data-bs-toggle="collapse" data-bs-target="#%s" aria-expanded="false" aria-controls="%s">' .
'<span class="icon-exclamation-circle" style="font-size:1.2rem;"></span>&nbsp;<small>%s</small></a>',
$uid,
$uid,
Text::_('COM_COMPONENTBUILDER_DETAILS')
);
$details = sprintf(
'<div class="collapse mt-2" id="%s"><ul class="list-unstyled text-danger small ps-3">%s</ul></div>',
$uid,
implode('', $bucket)
);
$errors['error'] .= $toggle . $details;
return $errors;
}
}

View File

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

View File

@@ -0,0 +1,107 @@
<?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\Interfaces\Git\Repository;
use VDM\Joomla\Interfaces\Git\ApiInterface;
/**
* The Git Repository Tags Interface
*
* @since 5.1.1
*/
interface TagsInterface extends ApiInterface
{
/**
* List a repository's tags
*
* @param string $owner The owner of the repo.
* @param string $repo The name of the repo.
* @param int|null $page The page number of results to return (1-based).
* @param int|null $limit The page size of results, default maximum page size is 10.
*
* @return array|null
* @since 3.2.0
**/
public function list(
string $owner,
string $repo,
?int $page = 1,
?int $limit = 10
): ?array;
/**
* Get the tag of a repository by tag name.
*
* @param string $owner The owner name.
* @param string $repo The repository name.
* @param string $tag The tag name.
*
* @return object|null
* @since 3.2.0
**/
public function get(string $owner, string $repo, string $tag): ?object;
/**
* Get the tag object of an annotated tag (not lightweight tags).
*
* @param string $owner The owner of the repo.
* @param string $repo The name of the repo.
* @param string $sha The sha of the tag. The Git tags API only supports annotated tag objects, not lightweight tags.
*
* @return object|null
* @since 3.2.0
**/
public function sha(
string $owner,
string $repo,
string $sha
): ?object;
/**
* Create a new git tag in a repository.
*
* @param string $owner The owner of the repo.
* @param string $repo The name of the repo.
* @param string $tagName The name of the tag.
* @param string $target The SHA of the git object this is tagging.
* @param string $message The tag message.
*
* @return object|null
* @since 3.2.0
**/
public function create(
string $owner,
string $repo,
string $tagName,
string $target,
string $message
): ?object;
/**
* Delete a repository's tag by name.
*
* @param string $owner The owner name.
* @param string $repo The repository name.
* @param string $tag The tag name.
*
* @return string
* @since 3.2.0
**/
public function delete(
string $owner,
string $repo,
string $tag
): string;
}

View File

@@ -0,0 +1,135 @@
<?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\Interfaces\Git\Repository;
use VDM\Joomla\Interfaces\Git\ApiInterface;
/**
* The Git Repository Wiki Interface
*
* @since 5.1.1
*/
interface WikiInterface extends ApiInterface
{
/**
* Create a wiki page.
*
* @param string $owner The owner name.
* @param string $repo The repository name.
* @param string $title The title of the wiki page.
* @param string $contentBase64 The base64 encoded content of the wiki page.
* @param string|null $message Optional commit message summarizing the change.
*
* @return object|null
* @since 3.2.0
**/
public function create(
string $owner,
string $repo,
string $title,
string $contentBase64,
?string $message = null
): ?object;
/**
* Get a wiki page.
*
* @param string $owner The owner name.
* @param string $repo The repository name.
* @param string $pageName The name of the wiki page.
*
* @return object|null
* @since 3.2.0
**/
public function get(
string $owner,
string $repo,
string $pageName
): ?object;
/**
* Get all wiki pages.
*
* @param string $owner The owner name.
* @param string $repo The repository name.
* @param int $page Page number of results to return (1-based).
* @param int $limit Page size of results.
*
* @return array|null
* @since 3.2.0
**/
public function pages(
string $owner,
string $repo,
int $page = 1,
int $limit = 10
): ?array;
/**
* Delete a wiki page.
*
* @param string $owner The owner name.
* @param string $repo The repository name.
* @param string $pageName The name of the wiki page.
*
* @return string
* @since 3.2.0
**/
public function delete(
string $owner,
string $repo,
string $pageName
): string;
/**
* Edit a wiki page.
*
* @param string $owner The owner name.
* @param string $repo The repository name.
* @param string $pageName The name of the wiki page.
* @param string $title The new title of the wiki page.
* @param string $content The new content of the wiki page.
* @param string $message The optional commit message summarizing the change.
*
* @return object|null
* @since 3.2.0
**/
public function edit(
string $owner,
string $repo,
string $pageName,
string $title,
string $content,
string $message = null
): ?object;
/**
* Get revisions of a wiki page.
*
* @param string $owner The owner name.
* @param string $repo The repository name.
* @param string $pageName The name of the wiki page.
* @param int $page The page number of results to return (1-based).
*
* @return object|null
* @since 3.2.0
**/
public function revisions(
string $owner,
string $repo,
string $pageName,
int $page = 1
): ?object;
}