Skip to content

Commit 546e910

Browse files
authored
Merge pull request #235 from dotkernel/develop
Update image paths and refactor directory structure
2 parents f055c9f + d2981fd commit 546e910

96 files changed

Lines changed: 77 additions & 256 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@ After changing `post_status`, follow the same steps: re-run `bin/doctrine-fixtur
5151

5252
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.).
5353

54-
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.
55-
56-
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.
54+
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.
5755

5856
## 3. At deploy - run in this order
5957

@@ -62,8 +60,8 @@ php bin/doctrine-fixtures
6260
php bin/create-uploads-dir
6361
```
6462

65-
- `bin/doctrine-fixtures` loads `articles_cleaned.json` into the database, creating the `Post` entity (with its database-generated UUID) for the new article.
66-
- `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.
63+
- `bin/doctrine-fixtures` loads `articles_cleaned.json` into the database, creating the `Post` entity for the new article.
64+
- `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.
6765

6866
## 4. Regenerate the public artifacts - any order
6967

@@ -93,7 +91,7 @@ Steps to edit an existing article (change its status, text, or both) and get the
9391
- `public/md-articles/{category-slug}/{article-slug}.md`
9492
- `src/Blog/templates/page/blog-resource/{category-slug}/{article-slug}.html.twig`
9593
- `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)
96-
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.
94+
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.
9795

9896
## How to move an article to a different category
9997

‎bin/create-uploads-dir‎

Lines changed: 36 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,26 @@
33

44
declare(strict_types=1);
55

6-
use Doctrine\ORM\EntityManager;
7-
use Light\Blog\Entity\Post;
8-
96
chdir(__DIR__ . '/../');
107

11-
require 'vendor/autoload.php';
12-
13-
$templatesDir = 'src/Blog/templates/page/blog-resource';
14-
$publicDir = 'public';
15-
$uploadsDir = $publicDir . '/uploads';
16-
$articleDir = $uploadsDir . '/article';
17-
$limit = null;
8+
$publicDir = 'public';
9+
$mdArticlesDir = $publicDir . '/md-articles';
10+
$articleDir = $publicDir . '/uploads/article';
1811

19-
if (isset($argv[1])) {
20-
if (! ctype_digit($argv[1]) || (int) $argv[1] < 1) {
21-
fwrite(STDERR, sprintf("Invalid file limit '%s'. Expected a positive integer.%s", $argv[1], PHP_EOL));
22-
exit(1);
23-
}
24-
$limit = (int) $argv[1];
12+
if (! is_dir($mdArticlesDir)) {
13+
fwrite(STDERR, sprintf("Directory '%s' not found%s", $mdArticlesDir, PHP_EOL));
14+
exit(1);
2515
}
2616

27-
if (! is_dir($templatesDir)) {
28-
fwrite(STDERR, sprintf("Directory '%s' not found%s", $templatesDir, PHP_EOL));
17+
if (! is_dir($articleDir) && ! mkdir($articleDir, 0775, true) && ! is_dir($articleDir)) {
18+
fwrite(STDERR, sprintf("Failed to create directory '%s'%s", $articleDir, PHP_EOL));
2919
exit(1);
3020
}
3121

32-
$container = require 'config/container.php';
33-
$entityManager = $container->get(EntityManager::class);
34-
$postRepository = $entityManager->getRepository(Post::class);
35-
3622
/**
3723
* @return array<string, string>
3824
*/
39-
function indexUploadSources(string $publicDir): array
25+
function indexUploadSources(string $publicDir, string $articleDir): array
4026
{
4127
$index = [];
4228

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

38+
$pathname = $file->getPathname();
39+
if (str_starts_with($pathname, $articleDir . '/')) {
40+
continue;
41+
}
42+
5243
$basename = $file->getFilename();
5344
if (! isset($index[$basename])) {
54-
$index[$basename] = $file->getPathname();
45+
$index[$basename] = $pathname;
5546
}
5647
}
5748

5849
return $index;
5950
}
6051

6152
/**
62-
* @return list<array{uuid: string, filename: string}>
53+
* @return list<string>
6354
*/
64-
function extractArticleImageRefs(string $contents): array
55+
function extractArticleImageFilenames(string $contents): array
6556
{
66-
$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';
67-
68-
preg_match_all($pattern, $contents, $matches, PREG_SET_ORDER);
57+
$pattern = '/uploads\/article\/([^\/)\]\s"\']+)/i';
6958

70-
$seen = [];
71-
$refs = [];
72-
73-
foreach ($matches as $match) {
74-
$uuid = strtolower($match[1]);
75-
$filename = $match[2];
76-
$key = $uuid . '/' . $filename;
77-
78-
if (isset($seen[$key])) {
79-
continue;
80-
}
81-
82-
$seen[$key] = true;
83-
$refs[] = ['uuid' => $uuid, 'filename' => $filename];
84-
}
59+
preg_match_all($pattern, $contents, $matches);
8560

86-
return $refs;
61+
return array_values(array_unique($matches[1]));
8762
}
8863

89-
$sourceIndex = indexUploadSources($publicDir);
64+
$sourceIndex = indexUploadSources($publicDir, $articleDir);
9065

91-
$pattern = '/~\s*article\.id\s*~\s*\'\/([^\']+)\'/';
92-
93-
$filesProcessed = 0;
94-
$dirsCreated = 0;
95-
$imagesCopied = 0;
96-
$imagesMissing = 0;
66+
$mdFilesProcessed = 0;
67+
$imagesCopied = 0;
68+
$imagesMissing = 0;
9769

98-
$templateIterator = new RecursiveIteratorIterator(
99-
new RecursiveDirectoryIterator($templatesDir, FilesystemIterator::SKIP_DOTS)
70+
$mdIterator = new RecursiveIteratorIterator(
71+
new RecursiveDirectoryIterator($mdArticlesDir, FilesystemIterator::SKIP_DOTS)
10072
);
10173

102-
foreach ($templateIterator as $file) {
103-
if ($limit !== null && $filesProcessed >= $limit) {
104-
break;
105-
}
106-
107-
if (! $file->isFile() || $file->getExtension() !== 'twig') {
74+
foreach ($mdIterator as $file) {
75+
if (! $file->isFile() || $file->getExtension() !== 'md') {
10876
continue;
10977
}
11078

111-
$path = $file->getPathname();
112-
$slug = preg_replace('/\.html\.twig$/', '', $file->getFilename());
113-
$contents = file_get_contents($path);
79+
$contents = file_get_contents($file->getPathname());
11480
if ($contents === false) {
11581
continue;
11682
}
11783

118-
preg_match_all($pattern, $contents, $matches);
119-
$filenames = array_unique($matches[1] ?? []);
84+
$filenames = extractArticleImageFilenames($contents);
12085
if ($filenames === []) {
12186
continue;
12287
}
12388

124-
$post = $postRepository->findOneBy(['slug' => $slug]);
125-
if ($post === null) {
126-
printf("No Post found for slug '%s' (%s), skipping%s", $slug, $path, PHP_EOL);
127-
continue;
128-
}
129-
130-
$filesProcessed++;
131-
132-
$targetDir = $articleDir . '/' . $post->getId()->toString();
133-
if (! is_dir($targetDir)) {
134-
if (! mkdir($targetDir, 0775, true) && ! is_dir($targetDir)) {
135-
fwrite(STDERR, sprintf("Failed to create directory '%s'%s", $targetDir, PHP_EOL));
136-
continue;
137-
}
138-
$dirsCreated++;
139-
}
89+
$mdFilesProcessed++;
14090

14191
foreach ($filenames as $filename) {
142-
$targetPath = $targetDir . '/' . $filename;
92+
$targetPath = $articleDir . '/' . $filename;
14393
if (file_exists($targetPath)) {
14494
continue;
14595
}
14696

14797
if (! isset($sourceIndex[$filename])) {
148-
printf("Source image '%s' not found for %s%s", $filename, $path, PHP_EOL);
98+
printf("Source image '%s' not found for %s%s", $filename, $file->getPathname(), PHP_EOL);
14999
$imagesMissing++;
150100
continue;
151101
}
@@ -159,139 +109,12 @@ foreach ($templateIterator as $file) {
159109
}
160110
}
161111

162-
$mdArticlesDir = 'public/md-articles';
163-
164-
$mdFilesProcessed = 0;
165-
$uuidsFixed = 0;
166-
$mdDirsCreated = 0;
167-
$mdImagesCopied = 0;
168-
$mdImagesMissing = 0;
169-
170-
if (is_dir($mdArticlesDir)) {
171-
$mdIterator = new RecursiveIteratorIterator(
172-
new RecursiveDirectoryIterator($mdArticlesDir, FilesystemIterator::SKIP_DOTS)
173-
);
174-
175-
foreach ($mdIterator as $file) {
176-
if (! $file->isFile() || $file->getExtension() !== 'md') {
177-
continue;
178-
}
179-
180-
$path = $file->getPathname();
181-
$relative = ltrim(substr($path, strlen($mdArticlesDir)), '/');
182-
if (substr_count($relative, '/') !== 1) {
183-
continue;
184-
}
185-
186-
$slug = preg_replace('/\.md$/', '', $file->getFilename());
187-
$contents = file_get_contents($path);
188-
if ($contents === false) {
189-
continue;
190-
}
191-
192-
$refs = extractArticleImageRefs($contents);
193-
if ($refs === []) {
194-
continue;
195-
}
196-
197-
$post = $postRepository->findOneBy(['slug' => $slug]);
198-
if ($post === null) {
199-
printf("No Post found for slug '%s' (%s), skipping%s", $slug, $path, PHP_EOL);
200-
continue;
201-
}
202-
203-
$mdFilesProcessed++;
204-
205-
$realUuid = $post->getId()->toString();
206-
$staleUuids = [];
207-
208-
foreach ($refs as $ref) {
209-
$refUuid = $ref['uuid'];
210-
$filename = $ref['filename'];
211-
212-
if ($refUuid !== $realUuid) {
213-
$staleUuids[$refUuid] = true;
214-
}
215-
216-
$targetDir = $articleDir . '/' . $realUuid;
217-
$targetPath = $targetDir . '/' . $filename;
218-
219-
if (file_exists($targetPath)) {
220-
continue;
221-
}
222-
223-
if (! is_dir($targetDir)) {
224-
if (! mkdir($targetDir, 0775, true) && ! is_dir($targetDir)) {
225-
fwrite(STDERR, sprintf("Failed to create directory '%s'%s", $targetDir, PHP_EOL));
226-
continue;
227-
}
228-
$mdDirsCreated++;
229-
}
230-
231-
$sourcePath = null;
232-
if ($refUuid !== $realUuid) {
233-
$stalePath = $articleDir . '/' . $refUuid . '/' . $filename;
234-
if (file_exists($stalePath)) {
235-
$sourcePath = $stalePath;
236-
}
237-
}
238-
239-
if ($sourcePath === null && isset($sourceIndex[$filename])) {
240-
$sourcePath = $sourceIndex[$filename];
241-
}
242-
243-
if ($sourcePath === null) {
244-
printf("Source image '%s' not found for %s%s", $filename, $path, PHP_EOL);
245-
$mdImagesMissing++;
246-
continue;
247-
}
248-
249-
if (! copy($sourcePath, $targetPath)) {
250-
fwrite(STDERR, sprintf("Failed to copy '%s' to '%s'%s", $sourcePath, $targetPath, PHP_EOL));
251-
continue;
252-
}
253-
254-
$mdImagesCopied++;
255-
}
256-
257-
if ($staleUuids !== []) {
258-
$updated = $contents;
259-
foreach (array_keys($staleUuids) as $staleUuid) {
260-
$updated = str_replace($staleUuid, $realUuid, $updated);
261-
}
262-
263-
if ($updated !== $contents) {
264-
if (file_put_contents($path, $updated) === false) {
265-
fwrite(STDERR, sprintf("Failed to update '%s'%s", $path, PHP_EOL));
266-
} else {
267-
$uuidsFixed++;
268-
printf("Corrected UUID in '%s'%s", $path, PHP_EOL);
269-
}
270-
}
271-
}
272-
}
273-
}
274-
275112
printf(
276-
"Done. %d twig template%s processed, %d director%s created, %d image%s copied, %d missing.%s"
277-
. " %d markdown file%s processed, %d UUID%s corrected, %d director%s created,"
278-
. " %d image%s copied, %d missing.%s",
279-
$filesProcessed,
280-
$filesProcessed === 1 ? '' : 's',
281-
$dirsCreated,
282-
$dirsCreated === 1 ? 'y' : 'ies',
113+
"Done. %d markdown file%s processed, %d image%s copied, %d missing.%s",
114+
$mdFilesProcessed,
115+
$mdFilesProcessed === 1 ? '' : 's',
283116
$imagesCopied,
284117
$imagesCopied === 1 ? '' : 's',
285118
$imagesMissing,
286-
PHP_EOL,
287-
$mdFilesProcessed,
288-
$mdFilesProcessed === 1 ? '' : 's',
289-
$uuidsFixed,
290-
$uuidsFixed === 1 ? '' : 's',
291-
$mdDirsCreated,
292-
$mdDirsCreated === 1 ? 'y' : 'ies',
293-
$mdImagesCopied,
294-
$mdImagesCopied === 1 ? '' : 's',
295-
$mdImagesMissing,
296119
PHP_EOL
297120
);

‎public/md-articles/architecture/configprovider-bootstrap-modern-php-applications.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ The ConfigProvider is automatically picked up by the framework during applicatio
8181

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

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

8686
## Benefits
8787

‎public/md-articles/best-practice/aptana-set-svn-keywords.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ For example if you want to set the svn keyword property ***Id***:
1616

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

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

2121
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*
2222

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

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

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

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

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

3333
## FAQ
3434

‎public/md-articles/best-practice/svn-keywords-setup-in-php-ide-zend-studio.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ For **above** Proprieties , apply **only** to project folder, **NOT** recursive
6262
2. Select **All resources**.
6363
3. Check the **Use filtration by the resource name** and add **Mask:** *.php.
6464

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

6767
## FAQ
6868

0 commit comments

Comments
 (0)