Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,7 @@ After changing `post_status`, follow the same steps: re-run `bin/doctrine-fixtur

Copy an existing set of these three files in the same category as a starting point, to match the established structure (FAQ block matching the `FAQPage` entries, etc.).

If the article body uses images (via `asset('uploads/article/' ~ article.id ~ '/filename.png')` in the `.html.twig`), just drop the image file anywhere under `public/uploads` - `bin/create-uploads-dir` (step 4) finds it by filename and copies it to the right place. No manual path/folder creation needed.

Markdown articles reference images with a literal path instead, e.g. `![](/uploads/article/{post-id}/filename.png)` - `{post-id}` there is just a placeholder for whatever UUID the `Post` has when you write the file. `bin/create-uploads-dir` also scans `.md` files: it resolves the real `Post` by slug and, if the UUID hardcoded in the file doesn't match the post's actual current UUID (which happens whenever fixtures assign it a new one, e.g. in a fresh environment), rewrites the file to the real UUID and copies the image into the correct directory.
If the article body uses images (via `asset('uploads/article/filename.png')` in the `.html.twig`, or `![](/uploads/article/filename.png)` in the `.md`), just drop the image file anywhere under `public/uploads` - `bin/create-uploads-dir` (step 4) finds it by filename and copies it into the flat `public/uploads/article/` directory if it isn't already there. No per-article subfolder or post ID/UUID involved - every article image lives directly under `public/uploads/article/filename.png`, so the same literal path works across environments without drifting.

## 3. At deploy - run in this order

Expand All @@ -62,8 +60,8 @@ php bin/doctrine-fixtures
php bin/create-uploads-dir
```

- `bin/doctrine-fixtures` loads `articles_cleaned.json` into the database, creating the `Post` entity (with its database-generated UUID) for the new article.
- `bin/create-uploads-dir` must run *after* it - it resolves the post by slug to get that UUID, creates `public/uploads/article/{post-id}/`, and copies each image referenced in the `.html.twig` there from wherever it already lives under `public/uploads`. It does the same for `.md` files, additionally correcting the UUID hardcoded in the file if it no longer matches the post's real one.
- `bin/doctrine-fixtures` loads `articles_cleaned.json` into the database, creating the `Post` entity for the new article.
- `bin/create-uploads-dir` scans every `.md` file under `public/md-articles/` for `/uploads/article/filename.ext` references and, for any that aren't already present in `public/uploads/article/`, copies the file there from wherever it already lives under `public/uploads` (matched by filename). It doesn't touch the database.

## 4. Regenerate the public artifacts - any order

Expand Down Expand Up @@ -93,7 +91,7 @@ Steps to edit an existing article (change its status, text, or both) and get the
- `public/md-articles/{category-slug}/{article-slug}.md`
- `src/Blog/templates/page/blog-resource/{category-slug}/{article-slug}.html.twig`
- `src/Blog/templates/page/JSON-LD/{category-slug}/{article-slug}.jsonld.twig` (only if it has hardcoded text outside of `article.*`/`meta.*` variables — most of its fields pull straight from the database and update automatically)
3. **Re-run the same commands as step 3 and step 4 above** (`bin/doctrine-fixtures`, then `bin/generate-feed` / `bin/sitemap` / `bin/generate-llms-full`) so the database and the generated artifacts reflect the change. `bin/create-uploads-dir` only needs to run again if you added a new image - it's also safe (and cheap) to run any time you suspect a `.md` file's hardcoded UUID has drifted from the post's real one.
3. **Re-run the same commands as step 3 and step 4 above** (`bin/doctrine-fixtures`, then `bin/generate-feed` / `bin/sitemap` / `bin/generate-llms-full`) so the database and the generated artifacts reflect the change. `bin/create-uploads-dir` only needs to run again if you added a new image.

## How to move an article to a different category

Expand Down
249 changes: 36 additions & 213 deletions bin/create-uploads-dir
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,26 @@

declare(strict_types=1);

use Doctrine\ORM\EntityManager;
use Light\Blog\Entity\Post;

chdir(__DIR__ . '/../');

require 'vendor/autoload.php';

$templatesDir = 'src/Blog/templates/page/blog-resource';
$publicDir = 'public';
$uploadsDir = $publicDir . '/uploads';
$articleDir = $uploadsDir . '/article';
$limit = null;
$publicDir = 'public';
$mdArticlesDir = $publicDir . '/md-articles';
$articleDir = $publicDir . '/uploads/article';

if (isset($argv[1])) {
if (! ctype_digit($argv[1]) || (int) $argv[1] < 1) {
fwrite(STDERR, sprintf("Invalid file limit '%s'. Expected a positive integer.%s", $argv[1], PHP_EOL));
exit(1);
}
$limit = (int) $argv[1];
if (! is_dir($mdArticlesDir)) {
fwrite(STDERR, sprintf("Directory '%s' not found%s", $mdArticlesDir, PHP_EOL));
exit(1);
}

if (! is_dir($templatesDir)) {
fwrite(STDERR, sprintf("Directory '%s' not found%s", $templatesDir, PHP_EOL));
if (! is_dir($articleDir) && ! mkdir($articleDir, 0775, true) && ! is_dir($articleDir)) {
fwrite(STDERR, sprintf("Failed to create directory '%s'%s", $articleDir, PHP_EOL));
exit(1);
}

$container = require 'config/container.php';
$entityManager = $container->get(EntityManager::class);
$postRepository = $entityManager->getRepository(Post::class);

/**
* @return array<string, string>
*/
function indexUploadSources(string $publicDir): array
function indexUploadSources(string $publicDir, string $articleDir): array
{
$index = [];

Expand All @@ -49,103 +35,67 @@ function indexUploadSources(string $publicDir): array
continue;
}

$pathname = $file->getPathname();
if (str_starts_with($pathname, $articleDir . '/')) {
continue;
}

$basename = $file->getFilename();
if (! isset($index[$basename])) {
$index[$basename] = $file->getPathname();
$index[$basename] = $pathname;
}
}

return $index;
}

/**
* @return list<array{uuid: string, filename: string}>
* @return list<string>
*/
function extractArticleImageRefs(string $contents): array
function extractArticleImageFilenames(string $contents): array
{
$pattern = '/uploads\/article\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\/([^)\]\s"\']+)/i';

preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER);
$pattern = '/uploads\/article\/([^\/)\]\s"\']+)/i';

$seen = [];
$refs = [];

foreach ($matches as $match) {
$uuid = strtolower($match[1]);
$filename = $match[2];
$key = $uuid . '/' . $filename;

if (isset($seen[$key])) {
continue;
}

$seen[$key] = true;
$refs[] = ['uuid' => $uuid, 'filename' => $filename];
}
preg_match_all($pattern, $contents, $matches);

return $refs;
return array_values(array_unique($matches[1]));
}

$sourceIndex = indexUploadSources($publicDir);
$sourceIndex = indexUploadSources($publicDir, $articleDir);

$pattern = '/~\s*article\.id\s*~\s*\'\/([^\']+)\'/';

$filesProcessed = 0;
$dirsCreated = 0;
$imagesCopied = 0;
$imagesMissing = 0;
$mdFilesProcessed = 0;
$imagesCopied = 0;
$imagesMissing = 0;

$templateIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($templatesDir, FilesystemIterator::SKIP_DOTS)
$mdIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($mdArticlesDir, FilesystemIterator::SKIP_DOTS)
);

foreach ($templateIterator as $file) {
if ($limit !== null && $filesProcessed >= $limit) {
break;
}

if (! $file->isFile() || $file->getExtension() !== 'twig') {
foreach ($mdIterator as $file) {
if (! $file->isFile() || $file->getExtension() !== 'md') {
continue;
}

$path = $file->getPathname();
$slug = preg_replace('/\.html\.twig$/', '', $file->getFilename());
$contents = file_get_contents($path);
$contents = file_get_contents($file->getPathname());
if ($contents === false) {
continue;
}

preg_match_all($pattern, $contents, $matches);
$filenames = array_unique($matches[1] ?? []);
$filenames = extractArticleImageFilenames($contents);
if ($filenames === []) {
continue;
}

$post = $postRepository->findOneBy(['slug' => $slug]);
if ($post === null) {
printf("No Post found for slug '%s' (%s), skipping%s", $slug, $path, PHP_EOL);
continue;
}

$filesProcessed++;

$targetDir = $articleDir . '/' . $post->getId()->toString();
if (! is_dir($targetDir)) {
if (! mkdir($targetDir, 0775, true) && ! is_dir($targetDir)) {
fwrite(STDERR, sprintf("Failed to create directory '%s'%s", $targetDir, PHP_EOL));
continue;
}
$dirsCreated++;
}
$mdFilesProcessed++;

foreach ($filenames as $filename) {
$targetPath = $targetDir . '/' . $filename;
$targetPath = $articleDir . '/' . $filename;
if (file_exists($targetPath)) {
continue;
}

if (! isset($sourceIndex[$filename])) {
printf("Source image '%s' not found for %s%s", $filename, $path, PHP_EOL);
printf("Source image '%s' not found for %s%s", $filename, $file->getPathname(), PHP_EOL);
$imagesMissing++;
continue;
}
Expand All @@ -159,139 +109,12 @@ foreach ($templateIterator as $file) {
}
}

$mdArticlesDir = 'public/md-articles';

$mdFilesProcessed = 0;
$uuidsFixed = 0;
$mdDirsCreated = 0;
$mdImagesCopied = 0;
$mdImagesMissing = 0;

if (is_dir($mdArticlesDir)) {
$mdIterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($mdArticlesDir, FilesystemIterator::SKIP_DOTS)
);

foreach ($mdIterator as $file) {
if (! $file->isFile() || $file->getExtension() !== 'md') {
continue;
}

$path = $file->getPathname();
$relative = ltrim(substr($path, strlen($mdArticlesDir)), '/');
if (substr_count($relative, '/') !== 1) {
continue;
}

$slug = preg_replace('/\.md$/', '', $file->getFilename());
$contents = file_get_contents($path);
if ($contents === false) {
continue;
}

$refs = extractArticleImageRefs($contents);
if ($refs === []) {
continue;
}

$post = $postRepository->findOneBy(['slug' => $slug]);
if ($post === null) {
printf("No Post found for slug '%s' (%s), skipping%s", $slug, $path, PHP_EOL);
continue;
}

$mdFilesProcessed++;

$realUuid = $post->getId()->toString();
$staleUuids = [];

foreach ($refs as $ref) {
$refUuid = $ref['uuid'];
$filename = $ref['filename'];

if ($refUuid !== $realUuid) {
$staleUuids[$refUuid] = true;
}

$targetDir = $articleDir . '/' . $realUuid;
$targetPath = $targetDir . '/' . $filename;

if (file_exists($targetPath)) {
continue;
}

if (! is_dir($targetDir)) {
if (! mkdir($targetDir, 0775, true) && ! is_dir($targetDir)) {
fwrite(STDERR, sprintf("Failed to create directory '%s'%s", $targetDir, PHP_EOL));
continue;
}
$mdDirsCreated++;
}

$sourcePath = null;
if ($refUuid !== $realUuid) {
$stalePath = $articleDir . '/' . $refUuid . '/' . $filename;
if (file_exists($stalePath)) {
$sourcePath = $stalePath;
}
}

if ($sourcePath === null && isset($sourceIndex[$filename])) {
$sourcePath = $sourceIndex[$filename];
}

if ($sourcePath === null) {
printf("Source image '%s' not found for %s%s", $filename, $path, PHP_EOL);
$mdImagesMissing++;
continue;
}

if (! copy($sourcePath, $targetPath)) {
fwrite(STDERR, sprintf("Failed to copy '%s' to '%s'%s", $sourcePath, $targetPath, PHP_EOL));
continue;
}

$mdImagesCopied++;
}

if ($staleUuids !== []) {
$updated = $contents;
foreach (array_keys($staleUuids) as $staleUuid) {
$updated = str_replace($staleUuid, $realUuid, $updated);
}

if ($updated !== $contents) {
if (file_put_contents($path, $updated) === false) {
fwrite(STDERR, sprintf("Failed to update '%s'%s", $path, PHP_EOL));
} else {
$uuidsFixed++;
printf("Corrected UUID in '%s'%s", $path, PHP_EOL);
}
}
}
}
}

printf(
"Done. %d twig template%s processed, %d director%s created, %d image%s copied, %d missing.%s"
. " %d markdown file%s processed, %d UUID%s corrected, %d director%s created,"
. " %d image%s copied, %d missing.%s",
$filesProcessed,
$filesProcessed === 1 ? '' : 's',
$dirsCreated,
$dirsCreated === 1 ? 'y' : 'ies',
"Done. %d markdown file%s processed, %d image%s copied, %d missing.%s",
$mdFilesProcessed,
$mdFilesProcessed === 1 ? '' : 's',
$imagesCopied,
$imagesCopied === 1 ? '' : 's',
$imagesMissing,
PHP_EOL,
$mdFilesProcessed,
$mdFilesProcessed === 1 ? '' : 's',
$uuidsFixed,
$uuidsFixed === 1 ? '' : 's',
$mdDirsCreated,
$mdDirsCreated === 1 ? 'y' : 'ies',
$mdImagesCopied,
$mdImagesCopied === 1 ? '' : 's',
$mdImagesMissing,
PHP_EOL
);
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ The ConfigProvider is automatically picked up by the framework during applicatio

Below you can see how Mezzio and Dotkernel merge and use ConfigProviders to build the middleware pipeline and dependencies.

![](/uploads/article/019f8a80-cc92-7277-92c8-c0e68d81615f/ConfigProvider2.png)
![](/uploads/article/ConfigProvider2.png)

## Benefits

Expand Down
8 changes: 4 additions & 4 deletions public/md-articles/best-practice/aptana-set-svn-keywords.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ For example if you want to set the svn keyword property ***Id***:

1. In the file where you want to add the svn keyword property write **$Id$**

![](/uploads/article/019f8a80-cc86-73d9-a427-0621b2a55777/id-file-300x235.gif)
![](/uploads/article/id-file-300x235.gif)

2. Right click on the file, then follow Team -> Set Property...**Note**: *Set Property...* will not be active if you haven't first added the file to SVN: *Team*->*Add to Version Controller*

![](/uploads/article/019f8a80-cc86-73d9-a427-0621b2a55777/set-property-300x152.gif)
![](/uploads/article/set-property-300x152.gif)

3. Select **svn:keywords**, and write **Id** in the text field

![](/uploads/article/019f8a80-cc86-73d9-a427-0621b2a55777/svn-keywords-300x298.gif)
![](/uploads/article/svn-keywords-300x298.gif)

When you make the SVN commit of the file, the *$Id$* keyword will be replaced with text in the format shown below:

![](/uploads/article/019f8a80-cc86-73d9-a427-0621b2a55777/id-file-svn-300x141.gif)
![](/uploads/article/id-file-svn-300x141.gif)

## FAQ

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ For **above** Proprieties , apply **only** to project folder, **NOT** recursive
2. Select **All resources**.
3. Check the **Use filtration by the resource name** and add **Mask:** *.php.

[![svn-add](/uploads/article/019f8a80-cc87-71b3-80b8-478826d88044/svn-add.jpg)](/uploads/2013/02/svn-add.jpg)
[![svn-add](/uploads/article/svn-add.jpg)](/uploads/2013/02/svn-add.jpg)

## FAQ

Expand Down
Loading