Skip to content

Commit 736ec35

Browse files
authored
Merge pull request phpbb#6847 from iMattPro/ticket/17541
[ticket/17541] Fix and optimize issues in ext catalog installer
2 parents 2ce5cff + a0aadd3 commit 736ec35

File tree

1 file changed

+155
-17
lines changed

1 file changed

+155
-17
lines changed

phpBB/phpbb/composer/installer.php

Lines changed: 155 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -390,22 +390,22 @@ protected function do_get_available_packages($type)
390390
}
391391
}
392392

393-
foreach ($compatible_packages as $name => $versions)
393+
foreach ($compatible_packages as $package_name => $package_versions)
394394
{
395395
// Determine the highest version of the package
396396
/** @var CompletePackage|CompleteAliasPackage $highest_version */
397397
$highest_version = null;
398398

399399
// Sort the versions array in descending order
400-
usort($versions, function ($a, $b)
400+
usort($package_versions, function ($a, $b)
401401
{
402402
return version_compare($b->getVersion(), $a->getVersion());
403403
});
404404

405405
// The first element in the sorted array is the highest version
406-
if (!empty($versions))
406+
if (!empty($package_versions))
407407
{
408-
$highest_version = $versions[0];
408+
$highest_version = $package_versions[0];
409409

410410
// If highest version is a non-numeric dev branch, it's an instance of CompleteAliasPackage,
411411
// so we need to get the package being aliased in order to show the true non-numeric version.
@@ -416,23 +416,23 @@ protected function do_get_available_packages($type)
416416
}
417417

418418
// Generates the entry
419-
$available[$name] = [];
420-
$available[$name]['name'] = $highest_version->getPrettyName();
421-
$available[$name]['display_name'] = $highest_version->getExtra()['display-name'];
422-
$available[$name]['composer_name'] = $highest_version->getName();
423-
$available[$name]['version'] = $highest_version->getPrettyVersion();
419+
$available[$package_name] = [];
420+
$available[$package_name]['name'] = $highest_version->getPrettyName();
421+
$available[$package_name]['display_name'] = $highest_version->getExtra()['display-name'];
422+
$available[$package_name]['composer_name'] = $highest_version->getName();
423+
$available[$package_name]['version'] = $highest_version->getPrettyVersion();
424424

425425
if ($highest_version instanceof CompletePackage)
426426
{
427-
$available[$name]['description'] = $highest_version->getDescription();
428-
$available[$name]['url'] = $highest_version->getHomepage();
429-
$available[$name]['authors'] = $highest_version->getAuthors();
427+
$available[$package_name]['description'] = $highest_version->getDescription();
428+
$available[$package_name]['url'] = $highest_version->getHomepage();
429+
$available[$package_name]['authors'] = $highest_version->getAuthors();
430430
}
431431
else
432432
{
433-
$available[$name]['description'] = '';
434-
$available[$name]['url'] = '';
435-
$available[$name]['authors'] = [];
433+
$available[$package_name]['description'] = '';
434+
$available[$package_name]['url'] = '';
435+
$available[$package_name]['authors'] = [];
436436
}
437437
}
438438

@@ -545,8 +545,6 @@ private function get_compatible_versions(array $compatible_packages, ConstraintI
545545
*/
546546
protected function generate_ext_json_file(array $packages)
547547
{
548-
$io = new NullIO();
549-
550548
$composer = $this->get_composer(null);
551549

552550
$core_packages = $this->get_core_packages($composer);
@@ -587,8 +585,148 @@ protected function generate_ext_json_file(array $packages)
587585
$lockFile->write([]);
588586
}
589587

588+
// First pass write: base file with requested packages as provided
590589
$json_file->write($ext_json_data);
591590
$this->ext_json_file_backup = $ext_json_file_backup;
591+
592+
// Second pass: resolve and pin the highest compatible versions for unconstrained requested packages
593+
try
594+
{
595+
// Build a list of requested packages without explicit constraints
596+
$unconstrained = [];
597+
foreach ($packages as $name => $constraint)
598+
{
599+
// The $packages array can be either ['vendor/package' => '^1.2'] or ['vendor/package'] (numeric keys).
600+
if (is_int($name))
601+
{
602+
// Numeric key means just a name
603+
$package_name = $constraint;
604+
$unconstrained[$package_name] = true;
605+
}
606+
else
607+
{
608+
// If constraint is empty or '*' treat as unconstrained
609+
if ($constraint === '' || $constraint === '*' || $constraint === null)
610+
{
611+
$unconstrained[$name] = true;
612+
}
613+
}
614+
}
615+
616+
if (!empty($unconstrained))
617+
{
618+
// Load composer on the just-written file so repositories and core constraints are available
619+
$ext_composer = $this->get_composer($this->get_composer_ext_json_filename());
620+
621+
/** @var ConstraintInterface $core_constraint */
622+
$core_constraint = $ext_composer->getPackage()->getRequires()['phpbb/phpbb']->getConstraint();
623+
$core_stability = $ext_composer->getPackage()->getMinimumStability();
624+
625+
// Resolve highest compatible versions for each unconstrained package
626+
$pins = $this->resolve_highest_versions(array_keys($unconstrained), $ext_composer, $core_constraint, $core_stability);
627+
628+
if (!empty($pins))
629+
{
630+
// Merge pins into require section, overwriting unconstrained entries
631+
foreach ($pins as $pkg => $version)
632+
{
633+
$ext_json_data['require'][$pkg] = $version;
634+
}
635+
636+
// Rewrite composer-ext.json with pinned versions
637+
$json_file->write($ext_json_data);
638+
}
639+
}
640+
}
641+
catch (\Exception $e)
642+
{
643+
// If resolution fails for any reason, keep the first-pass file intact (Composer will still resolve).
644+
// Intentionally swallow to avoid breaking installation flow.
645+
}
646+
}
647+
648+
/**
649+
* Resolve the highest compatible versions for the given package names
650+
* based on repositories and phpBB/PHP constraints from the provided Composer instance.
651+
*
652+
* @param array $package_names list of package names to resolve
653+
* @param Composer|PartialComposer $composer Composer instance configured with repositories
654+
* @param ConstraintInterface $core_constraint phpBB version constraint
655+
* @param string $core_stability minimum stability
656+
* @return array [packageName => prettyVersion]
657+
*/
658+
protected function resolve_highest_versions(array $package_names, $composer, ConstraintInterface $core_constraint, $core_stability): array
659+
{
660+
$compatible_packages = [];
661+
$repositories = $composer->getRepositoryManager()->getRepositories();
662+
663+
foreach ($repositories as $repository)
664+
{
665+
try
666+
{
667+
if ($repository instanceof ComposerRepository)
668+
{
669+
foreach ($package_names as $name)
670+
{
671+
$versions = $repository->findPackages($name);
672+
if (!empty($versions))
673+
{
674+
$compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $name, $versions);
675+
}
676+
}
677+
}
678+
else
679+
{
680+
// Preload and filter by name for non-composer repositories
681+
$package_name = [];
682+
foreach ($repository->getPackages() as $package)
683+
{
684+
$name = $package->getName();
685+
if (in_array($name, $package_names, true))
686+
{
687+
$package_name[$name][] = $package;
688+
}
689+
}
690+
691+
foreach ($package_name as $name => $versions)
692+
{
693+
$compatible_packages = $this->get_compatible_versions($compatible_packages, $core_constraint, $core_stability, $name, $versions);
694+
}
695+
}
696+
}
697+
catch (\Exception $e)
698+
{
699+
// If a repo fails, just skip it.
700+
continue;
701+
}
702+
}
703+
704+
$pins = [];
705+
foreach ($package_names as $name)
706+
{
707+
if (empty($compatible_packages[$name]))
708+
{
709+
continue;
710+
}
711+
712+
$package_versions = $compatible_packages[$name];
713+
714+
// Sort descending by normalized version
715+
usort($package_versions, function ($a, $b) {
716+
return version_compare($b->getVersion(), $a->getVersion());
717+
});
718+
719+
$highest = $package_versions[0];
720+
if ($highest instanceof CompleteAliasPackage)
721+
{
722+
$highest = $highest->getAliasOf();
723+
}
724+
725+
// Pin to the resolved highest compatible version using its pretty version
726+
$pins[$name] = $highest->getPrettyVersion();
727+
}
728+
729+
return $pins;
592730
}
593731

594732
/**

0 commit comments

Comments
 (0)