diff --git a/src/Console/Commands/ChangeSourceDirectoryCommand.php b/src/Console/Commands/ChangeSourceDirectoryCommand.php
index 6bffde7f..2b9f8cee 100644
--- a/src/Console/Commands/ChangeSourceDirectoryCommand.php
+++ b/src/Console/Commands/ChangeSourceDirectoryCommand.php
@@ -27,7 +27,7 @@
class ChangeSourceDirectoryCommand extends Command
{
/** @var string */
- protected $signature = 'change:sourceDirectory {name : The new source directory name }';
+ protected $signature = 'change:sourceDirectory {name : The new source directory name}';
/** @var string */
protected $description = 'Change the source directory for your project';
diff --git a/src/Console/Commands/ServeCommand.php b/src/Console/Commands/ServeCommand.php
index 943b6528..bcb8e11a 100644
--- a/src/Console/Commands/ServeCommand.php
+++ b/src/Console/Commands/ServeCommand.php
@@ -101,7 +101,7 @@ protected function configureOutput(): void
protected function printStartMessage(): void
{
$this->useBasicOutput()
- ? $this->output->writeln('Starting the HydeRC server... Press Ctrl+C to stop')
+ ? $this->output->writeln('Starting the HydeRC server... Use Ctrl+C to stop')
: $this->console->printStartMessage($this->getHostSelection(), $this->getPortSelection(), $this->getEnvironmentVariables());
}
@@ -146,12 +146,7 @@ protected function checkArgvForOption(string $name): ?string
protected function openInBrowser(string $path = '/'): void
{
- $binary = match (PHP_OS_FAMILY) {
- 'Windows' => 'start',
- 'Darwin' => 'open',
- 'Linux' => 'xdg-open',
- default => null
- };
+ $binary = $this->getOpenCommand(PHP_OS_FAMILY);
$command = sprintf('%s http://%s:%d', $binary, $this->getHostSelection(), $this->getPortSelection());
$command = rtrim("$command/$path", '/');
@@ -164,4 +159,14 @@ protected function openInBrowser(string $path = '/'): void
$this->newLine();
}
}
+
+ protected function getOpenCommand(string $osFamily): ?string
+ {
+ return match ($osFamily) {
+ 'Windows' => 'start',
+ 'Darwin' => 'open',
+ 'Linux' => 'xdg-open',
+ default => null
+ };
+ }
}
diff --git a/src/Facades/Author.php b/src/Facades/Author.php
index 16976036..d9bbbb89 100644
--- a/src/Facades/Author.php
+++ b/src/Facades/Author.php
@@ -18,7 +18,7 @@ class Author
* Construct a new Post Author. For Hyde to discover this author,
* you must call this method from your hyde.php config file.
*
- * @see https://hydephp.com/docs/1.x/customization.html#authors
+ * @see https://hydephp.com/docs/1.x/customization#authors
*
* @param string $username The username of the author. This is the key used to find authors in the config.
* @param string|null $name The optional display name of the author, leave blank to use the username.
diff --git a/src/Facades/Filesystem.php b/src/Facades/Filesystem.php
index 3229bc58..2cbcf58b 100644
--- a/src/Facades/Filesystem.php
+++ b/src/Facades/Filesystem.php
@@ -69,6 +69,21 @@ public static function smartGlob(string $pattern, int $flags = 0): Collection
return self::kernel()->filesystem()->smartGlob($pattern, $flags);
}
+ /**
+ * Find files in the project's directory, with optional filtering by extension and recursion.
+ *
+ * The returned collection will be a list of paths relative to the project root.
+ *
+ * @param string $directory
+ * @param string|array|false $matchExtensions The file extension(s) to match, or false to match all files.
+ * @param bool $recursive Whether to search recursively or not.
+ * @return \Illuminate\Support\Collection
+ */
+ public static function findFiles(string $directory, string|array|false $matchExtensions = false, bool $recursive = false): Collection
+ {
+ return self::kernel()->filesystem()->findFiles($directory, $matchExtensions, $recursive);
+ }
+
/**
* Touch one or more files in the project's directory.
*
@@ -113,7 +128,7 @@ public static function unlinkIfExists(string $path): bool
*/
public static function getContents(string $path, bool $lock = false): string
{
- return self::get($path, $lock);
+ return self::get(...func_get_args());
}
/**
@@ -126,7 +141,7 @@ public static function getContents(string $path, bool $lock = false): string
*/
public static function putContents(string $path, string $contents, bool $lock = false): bool|int
{
- return self::put($path, $contents, $lock);
+ return self::put(...func_get_args());
}
protected static function filesystem(): \Illuminate\Filesystem\Filesystem
diff --git a/src/Foundation/Concerns/BaseFoundationCollection.php b/src/Foundation/Concerns/BaseFoundationCollection.php
index c11dbe9c..0b97b626 100644
--- a/src/Foundation/Concerns/BaseFoundationCollection.php
+++ b/src/Foundation/Concerns/BaseFoundationCollection.php
@@ -13,6 +13,11 @@
/**
* Base class for the kernel auto-discovery collections.
*
+ * @template TKey of array-key
+ * @template TValue
+ *
+ * @extends \Illuminate\Support\Collection
+ *
* These collections are the heart of the discovery process.
*
* They are responsible for discovering the files, pages, and routes,
diff --git a/src/Foundation/Concerns/ForwardsHyperlinks.php b/src/Foundation/Concerns/ForwardsHyperlinks.php
index 9d194fd5..7f5bef1d 100644
--- a/src/Foundation/Concerns/ForwardsHyperlinks.php
+++ b/src/Foundation/Concerns/ForwardsHyperlinks.php
@@ -5,6 +5,7 @@
namespace Hyde\Foundation\Concerns;
use Hyde\Support\Models\Route;
+use JetBrains\PhpStorm\Deprecated;
/**
* @internal Single-use trait for the HydeKernel class.
@@ -23,8 +24,14 @@ public function relativeLink(string $destination): string
return $this->hyperlinks->relativeLink($destination);
}
+ /**
+ * @deprecated This method will be removed in v2.0. Please use `asset()` instead.
+ */
+ #[Deprecated(reason: 'Use `asset` method instead.', replacement: '%class%::asset(%parameter0%)')]
public function mediaLink(string $destination, bool $validate = false): string
{
+ trigger_deprecation('hyde/framework', '1.8.0', 'The %s() method is deprecated, use %s() instead.', __METHOD__, 'asset');
+
return $this->hyperlinks->mediaLink($destination, $validate);
}
diff --git a/src/Foundation/Concerns/HandlesFoundationCollections.php b/src/Foundation/Concerns/HandlesFoundationCollections.php
index e9ef2625..58b137ed 100644
--- a/src/Foundation/Concerns/HandlesFoundationCollections.php
+++ b/src/Foundation/Concerns/HandlesFoundationCollections.php
@@ -15,7 +15,6 @@
*/
trait HandlesFoundationCollections
{
- /** @return \Hyde\Foundation\Kernel\FileCollection */
public function files(): FileCollection
{
$this->needsToBeBooted();
@@ -23,7 +22,6 @@ public function files(): FileCollection
return $this->files;
}
- /** @return \Hyde\Foundation\Kernel\PageCollection */
public function pages(): PageCollection
{
$this->needsToBeBooted();
@@ -31,7 +29,6 @@ public function pages(): PageCollection
return $this->pages;
}
- /** @return \Hyde\Foundation\Kernel\RouteCollection */
public function routes(): RouteCollection
{
$this->needsToBeBooted();
diff --git a/src/Foundation/Facades/Files.php b/src/Foundation/Facades/Files.php
index 39bbebb6..65e78467 100644
--- a/src/Foundation/Facades/Files.php
+++ b/src/Foundation/Facades/Files.php
@@ -13,7 +13,6 @@
*/
class Files extends Facade
{
- /** @return \Hyde\Foundation\Kernel\FileCollection */
public static function getFacadeRoot(): FileCollection
{
return HydeKernel::getInstance()->files();
diff --git a/src/Foundation/Facades/Pages.php b/src/Foundation/Facades/Pages.php
index a06577b7..42b37c66 100644
--- a/src/Foundation/Facades/Pages.php
+++ b/src/Foundation/Facades/Pages.php
@@ -13,7 +13,6 @@
*/
class Pages extends Facade
{
- /** @return \Hyde\Foundation\Kernel\PageCollection */
public static function getFacadeRoot(): PageCollection
{
return HydeKernel::getInstance()->pages();
diff --git a/src/Foundation/Facades/Routes.php b/src/Foundation/Facades/Routes.php
index 1a211480..f82acc26 100644
--- a/src/Foundation/Facades/Routes.php
+++ b/src/Foundation/Facades/Routes.php
@@ -19,7 +19,6 @@
*/
class Routes extends Facade
{
- /** @return \Hyde\Foundation\Kernel\RouteCollection */
public static function getFacadeRoot(): RouteCollection
{
return HydeKernel::getInstance()->routes();
@@ -41,7 +40,6 @@ public static function getOrFail(string $routeKey): Route
return static::getFacadeRoot()->getRoute($routeKey);
}
- /** @return \Hyde\Foundation\Kernel\RouteCollection<\Hyde\Support\Models\Route> */
public static function all(): RouteCollection
{
return static::getFacadeRoot()->getRoutes();
diff --git a/src/Foundation/HydeKernel.php b/src/Foundation/HydeKernel.php
index 1b7d8e5a..e6789e93 100644
--- a/src/Foundation/HydeKernel.php
+++ b/src/Foundation/HydeKernel.php
@@ -136,6 +136,7 @@ public function hasFeature(Feature|string $feature): bool
public function toArray(): array
{
return [
+ 'version' => self::VERSION,
'basePath' => $this->basePath,
'sourceRoot' => $this->sourceRoot,
'outputDirectory' => $this->outputDirectory,
diff --git a/src/Foundation/Internal/LoadYamlConfiguration.php b/src/Foundation/Internal/LoadYamlConfiguration.php
index d5d75acc..aae50195 100644
--- a/src/Foundation/Internal/LoadYamlConfiguration.php
+++ b/src/Foundation/Internal/LoadYamlConfiguration.php
@@ -26,6 +26,8 @@
class LoadYamlConfiguration
{
protected YamlConfigurationRepository $yaml;
+
+ /** @var array> */
protected array $config;
public function bootstrap(Application $app): void
@@ -33,7 +35,10 @@ public function bootstrap(Application $app): void
$this->yaml = $app->make(YamlConfigurationRepository::class);
if ($this->yaml->hasYamlConfigFile()) {
- tap($app->make('config'), function (Repository $config): void {
+ /** @var Repository $config */
+ $config = $app->make('config');
+
+ tap($config, function (Repository $config): void {
$this->config = $config->all();
$this->mergeParsedConfiguration();
})->set($this->config);
diff --git a/src/Foundation/Kernel/FileCollection.php b/src/Foundation/Kernel/FileCollection.php
index 063d04b0..49c67e49 100644
--- a/src/Foundation/Kernel/FileCollection.php
+++ b/src/Foundation/Kernel/FileCollection.php
@@ -4,13 +4,13 @@
namespace Hyde\Foundation\Kernel;
+use Hyde\Facades\Filesystem;
use Hyde\Foundation\Concerns\BaseFoundationCollection;
use Hyde\Framework\Exceptions\FileNotFoundException;
use Hyde\Pages\Concerns\HydePage;
use Hyde\Support\Filesystem\SourceFile;
use function basename;
-use function glob;
use function str_starts_with;
/**
@@ -18,7 +18,7 @@
*
* @template T of \Hyde\Support\Filesystem\SourceFile
*
- * @template-extends \Hyde\Foundation\Concerns\BaseFoundationCollection
+ * @extends \Hyde\Foundation\Concerns\BaseFoundationCollection
*
* @property array $items The files in the collection.
*
@@ -59,7 +59,7 @@ protected function runExtensionHandlers(): void
protected function discoverFilesFor(string $pageClass): void
{
// Scan the source directory, and directories therein, for files that match the model's file extension.
- foreach (glob($this->kernel->path($pageClass::sourcePath('{*,**/*}')), GLOB_BRACE) as $path) {
+ foreach (Filesystem::findFiles($pageClass::sourceDirectory(), $pageClass::fileExtension(), true) as $path) {
if (! str_starts_with(basename((string) $path), '_')) {
$this->addFile(SourceFile::make($path, $pageClass));
}
@@ -71,10 +71,7 @@ public function getFile(string $path): SourceFile
return $this->get($path) ?? throw new FileNotFoundException($path);
}
- /**
- * @param class-string<\Hyde\Pages\Concerns\HydePage>|null $pageClass
- * @return \Hyde\Foundation\Kernel\FileCollection
- */
+ /** @param class-string<\Hyde\Pages\Concerns\HydePage>|null $pageClass */
public function getFiles(?string $pageClass = null): FileCollection
{
return $pageClass ? $this->filter(function (SourceFile $file) use ($pageClass): bool {
diff --git a/src/Foundation/Kernel/Filesystem.php b/src/Foundation/Kernel/Filesystem.php
index a8f36a32..c4532c40 100644
--- a/src/Foundation/Kernel/Filesystem.php
+++ b/src/Foundation/Kernel/Filesystem.php
@@ -8,6 +8,7 @@
use Hyde\Foundation\HydeKernel;
use Hyde\Foundation\PharSupport;
use Illuminate\Support\Collection;
+use Hyde\Framework\Actions\Internal\FileFinder;
use function collect;
use function Hyde\normalize_slashes;
@@ -183,4 +184,16 @@ public function smartGlob(string $pattern, int $flags = 0): Collection
return $files->map(fn (string $path): string => $this->pathToRelative($path));
}
+
+ /**
+ * @param string|array|false $matchExtensions
+ * @return \Illuminate\Support\Collection
+ */
+ public function findFiles(string $directory, string|array|false $matchExtensions = false, bool $recursive = false): Collection
+ {
+ /** @var \Hyde\Framework\Actions\Internal\FileFinder $finder */
+ $finder = app(FileFinder::class);
+
+ return $finder->handle($directory, $matchExtensions, $recursive);
+ }
}
diff --git a/src/Foundation/Kernel/Hyperlinks.php b/src/Foundation/Kernel/Hyperlinks.php
index 6b2fd20e..2a94165d 100644
--- a/src/Foundation/Kernel/Hyperlinks.php
+++ b/src/Foundation/Kernel/Hyperlinks.php
@@ -7,6 +7,7 @@
use Hyde\Facades\Config;
use Hyde\Support\Models\Route;
use Hyde\Foundation\HydeKernel;
+use JetBrains\PhpStorm\Deprecated;
use Hyde\Framework\Exceptions\BaseUrlNotSetException;
use Hyde\Framework\Exceptions\FileNotFoundException;
use Illuminate\Support\Str;
@@ -92,7 +93,10 @@ public function relativeLink(string $destination): string
*
* An exception will be thrown if the file does not exist in the _media directory,
* and the second argument is set to true.
+ *
+ * @deprecated This method will be removed in v2.0. Please use `asset()` instead.
*/
+ #[Deprecated(reason: 'Use `asset` method instead.', replacement: '%class%->asset(%parameter0%)')]
public function mediaLink(string $destination, bool $validate = false): string
{
if ($validate && ! file_exists($sourcePath = "{$this->kernel->getMediaDirectory()}/$destination")) {
@@ -111,7 +115,7 @@ public function mediaLink(string $destination, bool $validate = false): string
*/
public function asset(string $name, bool $preferQualifiedUrl = false): string
{
- if (str_starts_with($name, 'http')) {
+ if (static::isRemote($name)) {
return $name;
}
@@ -147,7 +151,7 @@ public function url(string $path = ''): string
{
$path = $this->formatLink(trim($path, '/'));
- if (str_starts_with($path, 'http')) {
+ if (static::isRemote($path)) {
return $path;
}
@@ -173,4 +177,12 @@ public function route(string $key): ?Route
{
return $this->kernel->routes()->get($key);
}
+
+ /**
+ * Determine if the given URL is a remote link.
+ */
+ public static function isRemote(string $url): bool
+ {
+ return str_starts_with($url, 'http') || str_starts_with($url, '//');
+ }
}
diff --git a/src/Foundation/Kernel/PageCollection.php b/src/Foundation/Kernel/PageCollection.php
index 4739a177..4cc4d85e 100644
--- a/src/Foundation/Kernel/PageCollection.php
+++ b/src/Foundation/Kernel/PageCollection.php
@@ -14,7 +14,7 @@
*
* @template T of \Hyde\Pages\Concerns\HydePage
*
- * @template-extends \Hyde\Foundation\Concerns\BaseFoundationCollection
+ * @extends \Hyde\Foundation\Concerns\BaseFoundationCollection
*
* @property array $items The pages in the collection.
*
@@ -59,10 +59,7 @@ public function getPage(string $sourcePath): HydePage
return $this->get($sourcePath) ?? throw new FileNotFoundException($sourcePath);
}
- /**
- * @param class-string<\Hyde\Pages\Concerns\HydePage>|null $pageClass
- * @return \Hyde\Foundation\Kernel\PageCollection
- */
+ /** @param class-string<\Hyde\Pages\Concerns\HydePage>|null $pageClass */
public function getPages(?string $pageClass = null): PageCollection
{
return $pageClass ? $this->filter(function (HydePage $page) use ($pageClass): bool {
diff --git a/src/Foundation/Kernel/RouteCollection.php b/src/Foundation/Kernel/RouteCollection.php
index a93b8eab..fe71995a 100644
--- a/src/Foundation/Kernel/RouteCollection.php
+++ b/src/Foundation/Kernel/RouteCollection.php
@@ -15,7 +15,7 @@
*
* @template T of \Hyde\Support\Models\Route
*
- * @template-extends \Hyde\Foundation\Concerns\BaseFoundationCollection
+ * @extends \Hyde\Foundation\Concerns\BaseFoundationCollection
*
* @property array $items The routes in the collection.
*
@@ -53,10 +53,7 @@ public function getRoute(string $routeKey): Route
return $this->get($routeKey) ?? throw new RouteNotFoundException($routeKey);
}
- /**
- * @param class-string<\Hyde\Pages\Concerns\HydePage>|null $pageClass
- * @return \Hyde\Foundation\Kernel\RouteCollection
- */
+ /** @param class-string<\Hyde\Pages\Concerns\HydePage>|null $pageClass */
public function getRoutes(?string $pageClass = null): RouteCollection
{
return $pageClass ? $this->filter(function (Route $route) use ($pageClass): bool {
diff --git a/src/Framework/Actions/BladeMatterParser.php b/src/Framework/Actions/BladeMatterParser.php
index 2f2dcc6e..df3c402d 100644
--- a/src/Framework/Actions/BladeMatterParser.php
+++ b/src/Framework/Actions/BladeMatterParser.php
@@ -4,10 +4,9 @@
namespace Hyde\Framework\Actions;
-use Hyde\Hyde;
use RuntimeException;
+use Hyde\Facades\Filesystem;
-use function file_get_contents;
use function str_ends_with;
use function str_starts_with;
use function substr_count;
@@ -53,7 +52,7 @@ class BladeMatterParser
public static function parseFile(string $path): array
{
- return static::parseString(file_get_contents(Hyde::path($path)));
+ return static::parseString(Filesystem::getContents($path));
}
public static function parseString(string $contents): array
diff --git a/src/Framework/Actions/Internal/FileFinder.php b/src/Framework/Actions/Internal/FileFinder.php
new file mode 100644
index 00000000..7647b23a
--- /dev/null
+++ b/src/Framework/Actions/Internal/FileFinder.php
@@ -0,0 +1,66 @@
+|string|false $matchExtensions
+ * @return \Illuminate\Support\Collection
+ */
+ public static function handle(string $directory, array|string|false $matchExtensions = false, bool $recursive = false): Collection
+ {
+ if (! Filesystem::isDirectory($directory)) {
+ return collect();
+ }
+
+ $finder = Finder::create()->files()->in(Hyde::path($directory));
+
+ if ($recursive === false) {
+ $finder->depth('== 0');
+ }
+
+ if ($matchExtensions !== false) {
+ $finder->name(static::buildFileExtensionPattern((array) $matchExtensions));
+ }
+
+ return collect($finder)->map(function (SplFileInfo $file): string {
+ return Hyde::pathToRelative($file->getPathname());
+ })->sort()->values();
+ }
+
+ /** @param array $extensions */
+ protected static function buildFileExtensionPattern(array $extensions): string
+ {
+ $extensions = self::expandCommaSeparatedValues($extensions);
+
+ return '/\.('.self::normalizeExtensionForRegexPattern($extensions).')$/i';
+ }
+
+ /** @param array $extensions */
+ private static function expandCommaSeparatedValues(array $extensions): array
+ {
+ return array_merge(...array_map(function (string $item): array {
+ return array_map(fn (string $item): string => trim($item), explode(',', $item));
+ }, $extensions));
+ }
+
+ /** @param array $extensions */
+ private static function normalizeExtensionForRegexPattern(array $extensions): string
+ {
+ return implode('|', array_map(function (string $extension): string {
+ return preg_quote(ltrim($extension, '.'), '/');
+ }, $extensions));
+ }
+}
diff --git a/src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php b/src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php
index 849f077b..a0edd4d8 100644
--- a/src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php
+++ b/src/Framework/Actions/PreBuildTasks/CleanSiteDirectory.php
@@ -10,7 +10,6 @@
use Hyde\Framework\Features\BuildTasks\PreBuildTask;
use function basename;
-use function glob;
use function in_array;
use function sprintf;
@@ -21,7 +20,7 @@ class CleanSiteDirectory extends PreBuildTask
public function handle(): void
{
if ($this->isItSafeToCleanOutputDirectory()) {
- Filesystem::unlink(glob(Hyde::sitePath('*.{html,json}'), GLOB_BRACE));
+ Filesystem::unlink(Filesystem::findFiles(Hyde::sitePath(), ['html', 'json'])->all());
Filesystem::cleanDirectory(Hyde::siteMediaPath());
}
}
diff --git a/src/Framework/Factories/FeaturedImageFactory.php b/src/Framework/Factories/FeaturedImageFactory.php
index 2ecbbf27..4c2fdb01 100644
--- a/src/Framework/Factories/FeaturedImageFactory.php
+++ b/src/Framework/Factories/FeaturedImageFactory.php
@@ -8,6 +8,7 @@
use RuntimeException;
use Illuminate\Support\Str;
use Hyde\Markdown\Models\FrontMatter;
+use Hyde\Foundation\Kernel\Hyperlinks;
use Hyde\Framework\Features\Blogging\Models\FeaturedImage;
use Hyde\Markdown\Contracts\FrontMatter\SubSchemas\FeaturedImageSchema;
@@ -72,7 +73,7 @@ protected function makeSource(): string
throw new RuntimeException(sprintf('No featured image source was found in "%s"', $this->filePath ?? 'unknown file'));
}
- if (FeaturedImage::isRemote($value)) {
+ if (Hyperlinks::isRemote($value)) {
return $value;
}
diff --git a/src/Framework/Features/Blogging/Models/FeaturedImage.php b/src/Framework/Features/Blogging/Models/FeaturedImage.php
index a8dcb492..ebb3522c 100644
--- a/src/Framework/Features/Blogging/Models/FeaturedImage.php
+++ b/src/Framework/Features/Blogging/Models/FeaturedImage.php
@@ -9,7 +9,9 @@
use Hyde\Facades\Config;
use Illuminate\Support\Str;
use Hyde\Support\BuildWarnings;
+use JetBrains\PhpStorm\Deprecated;
use Illuminate\Support\Facades\Http;
+use Hyde\Foundation\Kernel\Hyperlinks;
use Hyde\Framework\Exceptions\FileNotFoundException;
use Hyde\Markdown\Contracts\FrontMatter\SubSchemas\FeaturedImageSchema;
@@ -19,7 +21,6 @@
use function filesize;
use function sprintf;
use function key;
-use function str_starts_with;
/**
* Object representation of a blog post's featured image.
@@ -63,7 +64,7 @@ public function __construct(
protected readonly ?string $licenseUrl = null,
protected readonly ?string $copyrightText = null
) {
- $this->type = self::isRemote($source) ? self::TYPE_REMOTE : self::TYPE_LOCAL;
+ $this->type = Hyperlinks::isRemote($source) ? self::TYPE_REMOTE : self::TYPE_LOCAL;
$this->source = $this->setSource($source);
}
@@ -241,8 +242,14 @@ protected function getContentLengthForRemoteImage(): int
return 0;
}
+ /**
+ * @codeCoverageIgnore Deprecated method.
+ *
+ * @deprecated This method will be removed in v2.0. Please use `Hyperlinks::isRemote` instead.
+ */
+ #[Deprecated(reason: 'Replaced by the \Hyde\Foundation\Kernel\Hyperlinks::isRemote method', replacement: '\Hyde\Foundation\Kernel\Hyperlinks::isRemote(%parametersList%)', since: '1.8.0')]
public static function isRemote(string $source): bool
{
- return str_starts_with($source, 'http') || str_starts_with($source, '//');
+ return Hyperlinks::isRemote($source);
}
}
diff --git a/src/Framework/Features/Blogging/Models/PostAuthor.php b/src/Framework/Features/Blogging/Models/PostAuthor.php
index ac6b850b..35236438 100644
--- a/src/Framework/Features/Blogging/Models/PostAuthor.php
+++ b/src/Framework/Features/Blogging/Models/PostAuthor.php
@@ -8,6 +8,7 @@
use Hyde\Facades\Author;
use Hyde\Facades\Config;
use Illuminate\Support\Collection;
+use JetBrains\PhpStorm\Deprecated;
use function strtolower;
use function is_string;
@@ -26,7 +27,7 @@ class PostAuthor implements Stringable
/**
* The display name of the author.
*/
- public readonly ?string $name;
+ public readonly string $name;
/**
* The author's website URL.
@@ -48,7 +49,7 @@ class PostAuthor implements Stringable
public function __construct(string $username, ?string $name = null, ?string $website = null)
{
$this->username = $username;
- $this->name = $name;
+ $this->name = $name ?? $this->username;
$this->website = $website;
}
@@ -82,12 +83,16 @@ public static function all(): Collection
public function __toString(): string
{
- return $this->getName();
+ return $this->name;
}
+ /**
+ * @deprecated This is not needed as the name property can be accessed directly.
+ */
+ #[Deprecated(reason: 'Use the name property instead.', replacement: '%class%->name')]
public function getName(): string
{
- return $this->name ?? $this->username;
+ return $this->name;
}
/** @param array{username?: string, name?: string, website?: string} $data */
diff --git a/src/Framework/Features/Metadata/MetadataBag.php b/src/Framework/Features/Metadata/MetadataBag.php
index ff8a036d..51e55491 100644
--- a/src/Framework/Features/Metadata/MetadataBag.php
+++ b/src/Framework/Features/Metadata/MetadataBag.php
@@ -54,26 +54,19 @@ public function get(): array
public function add(MetadataElementContract|string $element): static
{
- return match (true) {
- $element instanceof LinkElement => $this->addElement('links', $element),
- $element instanceof MetadataElement => $this->addElement('metadata', $element),
- $element instanceof OpenGraphElement => $this->addElement('properties', $element),
+ match (true) {
+ $element instanceof LinkElement => $this->links[$element->uniqueKey()] = $element,
+ $element instanceof MetadataElement => $this->metadata[$element->uniqueKey()] = $element,
+ $element instanceof OpenGraphElement => $this->properties[$element->uniqueKey()] = $element,
default => $this->addGenericElement((string) $element),
};
- }
-
- protected function addElement(string $type, MetadataElementContract $element): static
- {
- $this->{$type}[$element->uniqueKey()] = $element;
return $this;
}
- protected function addGenericElement(string $element): static
+ protected function addGenericElement(string $element): void
{
$this->generics[] = $element;
-
- return $this;
}
/** @return array */
diff --git a/src/Framework/Features/Metadata/PageMetadataBag.php b/src/Framework/Features/Metadata/PageMetadataBag.php
index 3aad1667..23a764b3 100644
--- a/src/Framework/Features/Metadata/PageMetadataBag.php
+++ b/src/Framework/Features/Metadata/PageMetadataBag.php
@@ -7,8 +7,8 @@
use Hyde\Facades\Meta;
use Hyde\Pages\Concerns\HydePage;
use Hyde\Pages\MarkdownPost;
+use Hyde\Foundation\Kernel\Hyperlinks;
-use function str_starts_with;
use function substr_count;
use function str_repeat;
@@ -34,6 +34,11 @@ protected function addDynamicPageMetadata(HydePage $page): void
$this->add(Meta::link('canonical', $page->getCanonicalUrl()));
}
+ if ($page->has('description')) {
+ $this->add(Meta::name('description', $page->data('description')));
+ $this->add(Meta::property('description', $page->data('description')));
+ }
+
if ($page->has('title')) {
$this->add(Meta::name('twitter:title', $page->title()));
$this->add(Meta::property('title', $page->title()));
@@ -46,7 +51,6 @@ protected function addDynamicPageMetadata(HydePage $page): void
protected function addMetadataForMarkdownPost(MarkdownPost $page): void
{
- $this->addPostMetadataIfExists($page, 'description');
$this->addPostMetadataIfExists($page, 'author');
$this->addPostMetadataIfExists($page, 'category', 'keywords');
@@ -77,7 +81,11 @@ protected function resolveImageLink(string $image): string
{
// Since this is run before the page is rendered, we don't have the currentPage property.
// So we need to run some of the same calculations here to resolve the image path link.
- return str_starts_with($image, 'http') ? $image
- : str_repeat('../', substr_count(MarkdownPost::outputDirectory().'/'.$this->page->identifier, '/')).$image;
+ return Hyperlinks::isRemote($image) ? $image : $this->calculatePathTraversal().$image;
+ }
+
+ private function calculatePathTraversal(): string
+ {
+ return str_repeat('../', substr_count(MarkdownPost::outputDirectory().'/'.$this->page->identifier, '/'));
}
}
diff --git a/src/Framework/Features/XmlGenerators/RssFeedGenerator.php b/src/Framework/Features/XmlGenerators/RssFeedGenerator.php
index 09ebc3db..b3abfab4 100644
--- a/src/Framework/Features/XmlGenerators/RssFeedGenerator.php
+++ b/src/Framework/Features/XmlGenerators/RssFeedGenerator.php
@@ -69,7 +69,7 @@ protected function addDynamicItemData(SimpleXMLElement $item, MarkdownPost $post
}
if (isset($post->author)) {
- $item->addChild('dc:creator', $post->author->getName(), 'http://purl.org/dc/elements/1.1/');
+ $item->addChild('dc:creator', $post->author->name, 'http://purl.org/dc/elements/1.1/');
}
if (isset($post->category)) {
diff --git a/src/Framework/Services/MarkdownService.php b/src/Framework/Services/MarkdownService.php
index aedd9949..4c9d3270 100644
--- a/src/Framework/Services/MarkdownService.php
+++ b/src/Framework/Services/MarkdownService.php
@@ -225,9 +225,7 @@ protected function enableAllHtmlElements(): void
], $this->config);
}
- /**
- * Normalize indentation for an un-compiled Markdown string.
- */
+ /** Normalize indentation for an un-compiled Markdown string */
public static function normalizeIndentationLevel(string $string): string
{
$lines = self::getNormalizedLines($string);
diff --git a/src/Hyde.php b/src/Hyde.php
index 01167b10..cc0df0dc 100644
--- a/src/Hyde.php
+++ b/src/Hyde.php
@@ -4,18 +4,8 @@
namespace Hyde;
-use Hyde\Enums\Feature;
-use Hyde\Facades\Features;
use Hyde\Foundation\HydeKernel;
-use Hyde\Foundation\Kernel\FileCollection;
-use Hyde\Foundation\Kernel\Filesystem;
-use Hyde\Foundation\Kernel\PageCollection;
-use Hyde\Foundation\Kernel\RouteCollection;
-use Hyde\Pages\Concerns\HydePage;
-use Hyde\Support\Models\Route;
-use Hyde\Support\Filesystem\SourceFile;
use Illuminate\Support\Facades\Facade;
-use Illuminate\Support\HtmlString;
use JetBrains\PhpStorm\Pure;
/**
@@ -27,50 +17,7 @@
* @copyright 2022 Caen De Silva
* @license MIT License
*
- * @method static string path(string $path = '')
- * @method static string vendorPath(string $path = '', string $package = 'framework')
- * @method static string pathToAbsolute(string $path)
- * @method static string pathToRelative(string $path)
- * @method static string sitePath(string $path = '')
- * @method static string mediaPath(string $path = '')
- * @method static string siteMediaPath(string $path = '')
- * @method static string formatLink(string $destination)
- * @method static string relativeLink(string $destination)
- * @method static string mediaLink(string $destination, bool $validate = false)
- * @method static string asset(string $name, bool $preferQualifiedUrl = false)
- * @method static string url(string $path = '')
- * @method static Route|null route(string $key)
- * @method static string makeTitle(string $value)
- * @method static string normalizeNewlines(string $string)
- * @method static string stripNewlines(string $string)
- * @method static string trimSlashes(string $string)
- * @method static HtmlString markdown(string $text, bool $stripIndentation = false)
- * @method static string currentPage()
- * @method static string currentRouteKey()
- * @method static string getBasePath()
- * @method static string getSourceRoot()
- * @method static string getOutputDirectory()
- * @method static string getMediaDirectory()
- * @method static string getMediaOutputDirectory()
- * @method static Features features()
- * @method static FileCollection files()
- * @method static PageCollection pages()
- * @method static RouteCollection routes()
- * @method static Route|null currentRoute()
- * @method static HydeKernel getInstance()
- * @method static Filesystem filesystem()
- * @method static array getRegisteredExtensions()
- * @method static bool hasFeature(Feature $feature)
- * @method static bool hasSiteUrl()
- * @method static void setInstance(HydeKernel $instance)
- * @method static void setBasePath(string $basePath)
- * @method static void setOutputDirectory(string $outputDirectory)
- * @method static void setMediaDirectory(string $mediaDirectory)
- * @method static void setSourceRoot(string $sourceRoot)
- * @method static void shareViewData(HydePage $page)
- * @method static array toArray()
- * @method static bool isBooted()
- * @method static void boot()
+ * @mixin \Hyde\Foundation\HydeKernel
*
* @see \Hyde\Foundation\Concerns\ForwardsFilesystem
* @see \Hyde\Foundation\Concerns\ForwardsHyperlinks
diff --git a/src/Markdown/Contracts/FrontMatter/PageSchema.php b/src/Markdown/Contracts/FrontMatter/PageSchema.php
index ac8eed79..2875b10d 100644
--- a/src/Markdown/Contracts/FrontMatter/PageSchema.php
+++ b/src/Markdown/Contracts/FrontMatter/PageSchema.php
@@ -13,7 +13,8 @@ interface PageSchema extends FrontMatterSchema
{
public const PAGE_SCHEMA = [
'title' => 'string',
- 'canonicalUrl' => 'string', // While not present in the page data, it is supported as a front matter key for the accessor data source.
+ 'description' => 'string', // For values. It is used by the automatic page metadata generator, which reads this value from the front matter.
+ 'canonicalUrl' => 'string', // While not present in the page data as a property, it is used by the accessor method, which reads this value from the front matter.
'navigation' => NavigationSchema::NAVIGATION_SCHEMA,
];
}
diff --git a/src/Markdown/MarkdownConverter.php b/src/Markdown/MarkdownConverter.php
index ef6265f1..26cb8902 100644
--- a/src/Markdown/MarkdownConverter.php
+++ b/src/Markdown/MarkdownConverter.php
@@ -9,8 +9,6 @@
/**
* The base Markdown converter class.
- *
- * "Extends" \League\CommonMark\CommonMarkConverter.
*/
class MarkdownConverter extends \League\CommonMark\MarkdownConverter
{
diff --git a/src/Pages/Concerns/HydePage.php b/src/Pages/Concerns/HydePage.php
index fb741358..08a730ea 100644
--- a/src/Pages/Concerns/HydePage.php
+++ b/src/Pages/Concerns/HydePage.php
@@ -397,6 +397,13 @@ public function navigationMenuGroup(): ?string
return $this->navigation->group;
}
+ /**
+ * Get the canonical URL for the page to use in the `` tag.
+ *
+ * It can be explicitly set in the front matter using the `canonicalUrl` key,
+ * otherwise it will be generated based on the site URL and the output path,
+ * unless there is no configured base URL, leading to this returning null.
+ */
public function getCanonicalUrl(): ?string
{
/** @var ?string $value */
diff --git a/src/Support/Concerns/Serializable.php b/src/Support/Concerns/Serializable.php
index f76c123f..9fc66cb7 100644
--- a/src/Support/Concerns/Serializable.php
+++ b/src/Support/Concerns/Serializable.php
@@ -14,8 +14,11 @@
*/
trait Serializable
{
- /** @inheritDoc */
- abstract public function toArray(): array;
+ /** Default implementation to dynamically serialize all public properties. Can be overridden for increased control. */
+ public function toArray(): array
+ {
+ return $this->automaticallySerialize();
+ }
/** Recursively serialize Arrayables */
public function arraySerialize(): array
@@ -34,4 +37,12 @@ public function toJson($options = 0): string
{
return json_encode($this->jsonSerialize(), $options);
}
+
+ /** Automatically serialize all public properties. */
+ protected function automaticallySerialize(): array
+ {
+ // Calling the function from a different scope means we only get the public properties.
+
+ return get_object_vars(...)->__invoke($this);
+ }
}
diff --git a/src/Support/DataCollections.php b/src/Support/DataCollections.php
index e05596c6..91a22906 100644
--- a/src/Support/DataCollections.php
+++ b/src/Support/DataCollections.php
@@ -12,9 +12,8 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
-use function implode;
+use function Hyde\path_join;
use function json_decode;
-use function sprintf;
use function Hyde\unslash;
use function str_starts_with;
@@ -102,9 +101,7 @@ public static function json(string $name, bool $asArray = false): static
protected static function findFiles(string $name, array|string $extensions): Collection
{
- return Filesystem::smartGlob(sprintf('%s/%s/*.{%s}',
- static::$sourceDirectory, $name, implode(',', (array) $extensions)
- ), GLOB_BRACE);
+ return Filesystem::findFiles(path_join(static::$sourceDirectory, $name), $extensions);
}
protected static function makeIdentifier(string $path): string
diff --git a/src/Support/Filesystem/MediaFile.php b/src/Support/Filesystem/MediaFile.php
index 34da4b51..09ab5247 100644
--- a/src/Support/Filesystem/MediaFile.php
+++ b/src/Support/Filesystem/MediaFile.php
@@ -4,6 +4,7 @@
namespace Hyde\Support\Filesystem;
+use Hyde\Facades\Filesystem;
use Hyde\Hyde;
use Hyde\Facades\Config;
use Hyde\Framework\Exceptions\FileNotFoundException;
@@ -14,12 +15,9 @@
use function array_merge;
use function array_keys;
use function filesize;
-use function implode;
use function pathinfo;
use function collect;
use function is_file;
-use function sprintf;
-use function glob;
/**
* File abstraction for a project media file.
@@ -27,7 +25,7 @@
class MediaFile extends ProjectFile
{
/** @var array The default extensions for media types */
- final public const EXTENSIONS = ['png', 'svg', 'jpg', 'jpeg', 'webp', 'gif', 'ico', 'css', 'js'];
+ final public const EXTENSIONS = ['png', 'svg', 'jpg', 'jpeg', 'gif', 'ico', 'css', 'js'];
/** @return array The array keys are the filenames relative to the _media/ directory */
public static function all(): array
@@ -104,15 +102,18 @@ protected static function discoverMediaAssetFiles(): array
})->all();
}
+ /** @return array */
protected static function getMediaAssetFiles(): array
{
- return glob(Hyde::path(static::getMediaGlobPattern()), GLOB_BRACE) ?: [];
+ return Filesystem::findFiles(Hyde::getMediaDirectory(), static::getMediaFileExtensions(), true)->all();
}
- protected static function getMediaGlobPattern(): string
+ /** @return array|string */
+ protected static function getMediaFileExtensions(): array|string
{
- return sprintf(Hyde::getMediaDirectory().'/{*,**/*,**/*/*}.{%s}', implode(',',
- Config::getArray('hyde.media_extensions', self::EXTENSIONS)
- ));
+ /** @var array|string $config */
+ $config = Config::get('hyde.media_extensions', self::EXTENSIONS);
+
+ return $config;
}
}
diff --git a/src/helpers.php b/src/helpers.php
index 743fffb8..14e69878 100644
--- a/src/helpers.php
+++ b/src/helpers.php
@@ -46,7 +46,7 @@ function asset(string $name, bool $preferQualifiedUrl = false): string
if (! function_exists('route')) {
/**
- * Get a page route by its key.
+ * Get a page route instance by its key. Casting it to a string will return a relative link to the page.
*/
function route(string $key): ?Hyde\Support\Models\Route
{