mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-09-25 01:53:03 +02:00
Synopsis : normalisation SÛRE des text ajouté par formulaire
- fins de ligne \r\n/\r → \n,
- blocs de lignes vides → \n\n, è
- espaces/tabulations/NBSP → espace unique,
- trim.
Les retours à la ligne simples intra-paragraphe sont LAISSÉS INTACTS :
une coupure de ligne peut être une vraie frontière de mot (→ espace)
ou une coupure du mot ("dyna\nmiques" → "dynamiques"),
indistinguables sans dictionnaire — automatiser corromprait l'entrée
("poursuivantsur").
Migration batch 045 (backfill des données existantes) RETIRÉE : le
nettoyage automatique fiable des synopsis existants est impossible.
Chaque modification est testée (277 tests PHPUnit).
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,13 +1,14 @@
|
||||
# TODO
|
||||
|
||||
> Last updated: 2026-08-26
|
||||
> Context: Docs refactor: verify docs/ accuracy against current xamxam codebase; update/refactor stale docs
|
||||
> Context: Fix end-user admin report: unexpected line breaks in some synopsis — resolved as undetectable/unfixable for existing data; only safe normalization on new inputs
|
||||
|
||||
## In Progress
|
||||
|
||||
## Pending
|
||||
|
||||
## Completed
|
||||
- [x] #normalize-synopsis-whitespace & Passages à la ligne inopinés dans les synopsis : jugés INDÉTECTABLES/non-corrigeables automatiquement (une coupure de ligne interne peut être une vraie frontière de mot → espace, ou une coupure du mot → à réjoindre ; indistinguables sans dictionnaire, toute tentative corrompt l'entrée p.ex. "poursuivantsur"). Migration 045 SUPPRIMÉE ; dites à l'admin qu'il n'y a pas de nettoyage fiable des données existantes. Conservation de la NORMALISATION SÛRE sur les nouvelles saisies : fin de ligne \r\n/\r → \n, pertes de lignes vides multiples → \n\n, espaces/tabulations/NBSP → espace unique, trim — retour à la ligne simple intra-paragraphe laissé intact.
|
||||
- [x] #audit-all-docs-and [!high] Audit all docs/ and classify accurate vs stale
|
||||
- [x] #rewrite-development-md-to-match [!high] Rewrite development.md to match current just dev / app/ layout / PHPUnit
|
||||
- [x] #rewrite-deployment-md-to-match [!high] Rewrite deployment.md to match just deploy / /var/www/xamxam/ / backup
|
||||
|
||||
@@ -609,12 +609,39 @@ class ThesisCreateController
|
||||
// ── Private: input helpers ────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Trim and strip HTML tags from a string value.
|
||||
* htmlspecialchars is applied at render time, not here.
|
||||
* Normalise a submitted string value.
|
||||
*
|
||||
* Only the UNAMBIGUOUS cleanups are applied here. We deliberately do NOT
|
||||
* auto-collapse or rejoin single newlines inside a paragraph: a line break
|
||||
* can be either a real word boundary (should become a space) or a word split
|
||||
* across the line ("dyna\nmiques", should be rejoined to "dynamiques"),
|
||||
* and these are indistinguishable without a dictionary. Trying to guess
|
||||
* corrupts valid input (e.g. "se poursuivant\nsur" → "se poursuivantsur"),
|
||||
* so such breaks are left untouched and must be fixed by the author.
|
||||
*
|
||||
* Responsibilities:
|
||||
* - strip HTML tags (htmlspecialchars is applied at render time, not here)
|
||||
* - normalise all line endings to \n (handles \r\n and bare \r)
|
||||
* - collapse runs of horizontal whitespace (spaces, tabs, NBSP) to one space
|
||||
* - collapse multiple blank lines into a single paragraph separator (\n\n)
|
||||
* - trim leading/trailing whitespace and blank lines
|
||||
*/
|
||||
private function sanitiseString(string $input): string
|
||||
{
|
||||
return strip_tags(trim($input));
|
||||
$input = strip_tags($input);
|
||||
|
||||
// Normalise all line endings to \n (handles \r\n and bare \r).
|
||||
$input = preg_replace('/\r\n|\r|\n/', "\n", $input);
|
||||
|
||||
// Collapse multiple blank lines into a single paragraph separator.
|
||||
$input = preg_replace("/\n{2,}/u", "\n\n", $input);
|
||||
|
||||
// Collapse runs of horizontal whitespace (spaces, tabs, NBSP gaps, …)
|
||||
// into a single space. Single newlines inside a paragraph are NOT
|
||||
// touched, for the reason explained above.
|
||||
$input = preg_replace('/[ \t\x0B\x00\xC2\xA0]+/u', ' ', $input);
|
||||
|
||||
return trim($input, " \t\n\r\0\x0B\xC2\xA0");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -263,6 +263,96 @@ class ThesisCreateValidationTest extends TestCase
|
||||
$this->assertStringContainsString('Bold synopsis', $data['synopsis']);
|
||||
}
|
||||
|
||||
// ── Whitespace normalisation (safe, unambiguous subset only) ────────────────
|
||||
// Single newlines inside a paragraph are deliberately LEFT untouched: they can
|
||||
// be a word boundary (→ space) or a word split across a line ("dyna\nmiques"
|
||||
// → "dynamiques") and are indistinguishable, so we never guess and risk
|
||||
// corrupting valid input (e.g. "se poursuivant\nsur" → "poursuivantsur").
|
||||
|
||||
public function testSingleIntraParagraphNewlineIsLeftUntouched(): void
|
||||
{
|
||||
// A mid-word line break is ambiguous (could be a real split or a wrap),
|
||||
// so we do not auto-alter it — the author is responsible for cleaning it.
|
||||
$post = $this->validPost();
|
||||
$post['synopsis'] = "…les dyn\namiques complexes…";
|
||||
|
||||
$data = $this->validate($post);
|
||||
$this->assertSame("…les dyn\namiques complexes…", $data['synopsis']);
|
||||
}
|
||||
|
||||
public function testWordWrapSingleNewlineNotMerged(): void
|
||||
{
|
||||
// We must NOT merge two real words across a line break ("poursuivantsur").
|
||||
$post = $this->validPost();
|
||||
$post['synopsis'] = "se poursuivant\nsur une seconde ligne";
|
||||
|
||||
$data = $this->validate($post);
|
||||
$this->assertSame(
|
||||
"se poursuivant\nsur une seconde ligne",
|
||||
$data['synopsis']
|
||||
);
|
||||
}
|
||||
|
||||
public function testLegitimateParagraphBreakPreserved(): void
|
||||
{
|
||||
$post = $this->validPost();
|
||||
$post['synopsis'] = "Premier paragraphe\n\nSecond paragraphe";
|
||||
|
||||
$data = $this->validate($post);
|
||||
$this->assertSame(
|
||||
"Premier paragraphe\n\nSecond paragraphe",
|
||||
$data['synopsis']
|
||||
);
|
||||
}
|
||||
|
||||
public function testMultipleBlankLinesCollapseToSingleParagraphBreak(): void
|
||||
{
|
||||
$post = $this->validPost();
|
||||
$post['synopsis'] = "A\n\n\n\nB";
|
||||
|
||||
$data = $this->validate($post);
|
||||
$this->assertSame("A\n\nB", $data['synopsis']);
|
||||
}
|
||||
|
||||
public function testMixedWindowsAndUnixLineEndingsNormalised(): void
|
||||
{
|
||||
$post = $this->validPost();
|
||||
$post['synopsis'] = "une pause\r\n\r\ner une future";
|
||||
|
||||
$data = $this->validate($post);
|
||||
// \r\n and \r line-endings all collapse to \n, and blank lines mark
|
||||
// a single paragraph separator.
|
||||
$this->assertSame("une pause\n\ner une future", $data['synopsis']);
|
||||
}
|
||||
|
||||
public function testConsecutiveSpacesAndTabsCollapse(): void
|
||||
{
|
||||
$post = $this->validPost();
|
||||
$post['synopsis'] = "Très espacé\t\ttab";
|
||||
|
||||
$data = $this->validate($post);
|
||||
$this->assertSame('Très espacé tab', $data['synopsis']);
|
||||
}
|
||||
|
||||
public function testNonBreakingSpacesNormalised(): void
|
||||
{
|
||||
$post = $this->validPost();
|
||||
// Word pasted NBSP (U+00A0) between words must become a regular space.
|
||||
$post['synopsis'] = "Premier\u{00A0}mot second mot";
|
||||
|
||||
$data = $this->validate($post);
|
||||
$this->assertSame('Premier mot second mot', $data['synopsis']);
|
||||
}
|
||||
|
||||
public function testSynopsisSingleLineFieldTrimmed(): void
|
||||
{
|
||||
$post = $this->validPost();
|
||||
$post['synopsis'] = " texte qui flotte ";
|
||||
|
||||
$data = $this->validate($post);
|
||||
$this->assertSame('texte qui flotte', $data['synopsis']);
|
||||
}
|
||||
|
||||
// ── Author processing ────────────────────────────────────────────────────
|
||||
|
||||
public function testMultipleAuthorsAreSorted(): void
|
||||
|
||||
Reference in New Issue
Block a user