#!/usr/bin/env php * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: moko-platform.CLI * INGROUP: moko-platform * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /cli/manifest_licensing.php * VERSION: 09.25.02 * BRIEF: Ensure licensing tags (updateservers, dlid) in Joomla extension manifests */ declare(strict_types=1); require_once __DIR__ . '/../lib/Enterprise/CliFramework.php'; use MokoEnterprise\{CliFramework, SourceResolver}; /** * Reads the block from .mokogitea/manifest.xml and ensures that the * Joomla extension manifest contains the correct and tags. * * manifest.xml licensing block example: * * * true * true * https://git.mokoconsulting.tech/{org}/{repo}/updates.xml * MyExtension Updates * * * Supports {org} and {repo} placeholders in update-server URL, resolved from * the manifest's block or git remote. */ class ManifestLicensingCli extends CliFramework { protected function configure(): void { $this->setDescription('Ensure licensing tags (updateservers, dlid) in Joomla extension manifests'); $this->addArgument('--path', 'Repository root path', '.'); $this->addArgument('--fix', 'Apply fixes (default: dry-run check only)', false); $this->addArgument('--github-output', 'Write results to $GITHUB_OUTPUT', false); } protected function run(): int { $root = realpath($this->getArgument('--path')) ?: $this->getArgument('--path'); $fix = (bool) $this->getArgument('--fix'); $ghOutput = (bool) $this->getArgument('--github-output'); // ── 1. Read manifest.xml ────────────────────────────────────────── $manifestFile = "{$root}/.mokogitea/manifest.xml"; if (!file_exists($manifestFile)) { $this->log('WARN', "No manifest.xml found at {$manifestFile}"); $this->outputResult($ghOutput, 'skipped', 'No manifest.xml'); return 0; } $xml = @simplexml_load_file($manifestFile); if ($xml === false) { $this->log('ERROR', "Failed to parse {$manifestFile}"); return 1; } // ── 2. Check if licensing is enabled ────────────────────────────── if (!isset($xml->licensing) || (string) ($xml->licensing->enabled ?? '') !== 'true') { $this->log('INFO', 'Licensing not enabled in manifest.xml — skipping'); $this->outputResult($ghOutput, 'skipped', 'Licensing not enabled'); return 0; } $licensingNode = $xml->licensing; $dlidEnabled = ((string) ($licensingNode->dlid ?? 'true')) === 'true'; $updateServerUrl = (string) ($licensingNode->{'update-server'} ?? ''); $updateServerName = (string) ($licensingNode->{'update-server-name'} ?? ''); // ── 3. Resolve placeholders ─────────────────────────────────────── $org = (string) ($xml->identity->org ?? ''); $repo = (string) ($xml->identity->name ?? ''); // Fallback to git remote if manifest doesn't have org/name if (empty($org) || empty($repo)) { $remote = trim((string) @shell_exec("cd " . escapeshellarg($root) . " && git remote get-url origin 2>/dev/null")); if (preg_match('#[/:]([^/]+)/([^/.]+?)(?:\.git)?$#', $remote, $m)) { if (empty($org)) { $org = $m[1]; } if (empty($repo)) { $repo = $m[2]; } } } // Default update server URL if not specified if (empty($updateServerUrl) && !empty($org) && !empty($repo)) { $updateServerUrl = "https://git.mokoconsulting.tech/{$org}/{$repo}/updates.xml"; } // Resolve {org} and {repo} placeholders $updateServerUrl = str_replace(['{org}', '{repo}'], [$org, $repo], $updateServerUrl); // Default server name from display-name or repo name if (empty($updateServerName)) { $displayName = (string) ($xml->identity->{'display-name'} ?? $repo); $updateServerName = $displayName . ' Updates'; } if (empty($updateServerUrl)) { $this->log('ERROR', 'Cannot determine update server URL — set in manifest.xml or ensure org/repo are available'); return 1; } $this->log('INFO', "Licensing enabled — org={$org}, repo={$repo}"); $this->log('INFO', "Update server: {$updateServerUrl}"); $this->log('INFO', "DLID required: " . ($dlidEnabled ? 'yes' : 'no')); // ── 4. Find Joomla extension manifests ──────────────────────────── $xmlFiles = array_merge( SourceResolver::globSource($root, '*.xml'), SourceResolver::globSource($root, 'packages/*/*.xml'), glob("{$root}/*.xml") ?: [] ); $packageManifest = null; foreach ($xmlFiles as $file) { $content = file_get_contents($file); if (!str_contains($content, 'log('WARN', 'No Joomla extension manifest found'); $this->outputResult($ghOutput, 'skipped', 'No extension manifest'); return 0; } $relPath = str_replace($root . '/', '', str_replace('\\', '/', $packageManifest)); $this->log('INFO', "Package manifest: {$relPath}"); // ── 5. Check and fix the manifest ───────────────────────────────── $content = file_get_contents($packageManifest); $original = $content; $changes = []; // --- 5a. Ensure block with correct URL --- if (preg_match('#\s*#s', $content)) { // Empty updateservers block — inject the server $replacement = "\n" . " {$updateServerUrl}\n" . " "; $content = preg_replace('#\s*#s', $replacement, $content); $changes[] = 'Added update server URL to empty '; } elseif (!str_contains($content, '')) { // No updateservers at all — add before $serverBlock = "\n \n" . " {$updateServerUrl}\n" . " \n"; $content = str_replace('', $serverBlock . '', $content); $changes[] = 'Added block'; } else { // updateservers exists — verify URL is correct if (preg_match('#]*>([^<]+)#', $content, $m)) { if ($m[1] !== $updateServerUrl) { $content = preg_replace( '#(]*>)[^<]+()#', "\${1}{$updateServerUrl}\${2}", $content ); $changes[] = "Updated server URL: {$m[1]} → {$updateServerUrl}"; } } } // --- 5b. Ensure tag if required --- if ($dlidEnabled) { if (!str_contains($content, ' if present, otherwise before $dlidTag = ' ' . "\n"; if (str_contains($content, '')) { $content = str_replace('', $dlidTag . "\n ", $content); } else { $content = str_replace('', $dlidTag . '', $content); } $changes[] = 'Added tag'; } } // --- 5c. Ensure for packages --- if (str_contains($content, 'type="package"') && !str_contains($content, '')) { $blockTag = ' true' . "\n"; if (str_contains($content, ' $content = preg_replace( '#(\s*\n)#', "\${1}{$blockTag}", $content ); } elseif (str_contains($content, '')) { $content = str_replace('', $blockTag . "\n ", $content); } else { $content = str_replace('', $blockTag . '', $content); } $changes[] = 'Added true'; } // ── 6. Report and apply ─────────────────────────────────────────── if (empty($changes)) { $this->log('INFO', 'All licensing tags are correct — no changes needed'); $this->outputResult($ghOutput, 'ok', 'No changes needed'); return 0; } foreach ($changes as $change) { $this->log($fix ? 'INFO' : 'WARN', ($fix ? 'Fixed: ' : 'Needs fix: ') . $change); } if ($fix) { file_put_contents($packageManifest, $content); $this->log('INFO', "Wrote {$relPath} with " . count($changes) . " change(s)"); $this->outputResult($ghOutput, 'fixed', implode('; ', $changes)); } else { $this->log('WARN', 'Run with --fix to apply changes'); $this->outputResult($ghOutput, 'needs-fix', implode('; ', $changes)); return 1; } return 0; } /** * Write result to $GITHUB_OUTPUT if requested. */ private function outputResult(bool $ghOutput, string $status, string $detail): void { if (!$ghOutput) { return; } $outputFile = getenv('GITHUB_OUTPUT'); if ($outputFile === false || $outputFile === '') { echo "licensing_status={$status}\n"; echo "licensing_detail={$detail}\n"; return; } $fh = fopen($outputFile, 'a'); fwrite($fh, "licensing_status={$status}\n"); fwrite($fh, "licensing_detail={$detail}\n"); fclose($fh); } } $app = new ManifestLicensingCli(); exit($app->execute());