diff --git a/.idea/php.xml b/.idea/php.xml
index 9891bef..3c96682 100644
--- a/.idea/php.xml
+++ b/.idea/php.xml
@@ -21,20 +21,16 @@
-
-
-
-
@@ -44,7 +40,6 @@
-
@@ -64,6 +59,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/CHANGES.md b/CHANGES.md
index 9a2877c..88437f3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,15 @@
+## Release 0.5.1p74 du 12/05/2025-15:34
+
+* `39b2fa5` générifier la méthode d'authentification
+* `33a10a3` ajout de table-sticky
+* `f7e692d` déplacer les fonctions vers nulib
+* `0c40769` améliorer Cursor
+* `a120143` possibilité de sauter une cellule
+* `fea5cb8` corrections tarray
+* `5862d35` maj doc
+
+## Release 0.5.0p82 du 30/04/2025-05:35
+
## Release 0.5.0p74 du 30/04/2025-05:30
* `2d73f4d` documenter showmorePlugin
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1b6942e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,48 @@
+# nur-ture
+
+
+## Release
+
+Exemple: release de la version 0.6.0
+
+Avant de faire une release majeure sur nur/ture, faire d'abord la release
+majeure correspondante sur
+* nulib/php
+* nulib/spout
+* nulib/phpss
+
+~~~sh
+version=0.6.0
+
+major="${version%.*}.0"
+
+## branche dev74
+git checkout dev74
+
+sed -ri "\
+/nulib\/.*:/s/[0-9]+.[0-9]+.0p74/${major}p74/
+" .composer.pman.yml
+pci "maj projet"
+
+prel -v$version
+
+# en cas de conflit, sélectionner HEAD
+_merge82
+
+## branche dev82
+git checkout dev82
+
+sed -ri "\
+/nulib\/.*:/s/[0-9]+.[0-9]+.0p82/${major}p82/
+" .composer.pman.yml
+pci "maj projet"
+
+prel -C
+
+commit="$(git log --grep="Init changelog . version ${version}p82" --format=%H)"
+git checkout dev74
+git cherry-pick "$commit"
+pp -a
+~~~
+
+-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
\ No newline at end of file
diff --git a/VERSION.txt b/VERSION.txt
index 8f0916f..4b9fcbe 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-0.5.0
+0.5.1
diff --git a/composer.lock b/composer.lock
index cb9584f..48205d6 100644
--- a/composer.lock
+++ b/composer.lock
@@ -585,11 +585,11 @@
},
{
"name": "nulib/php",
- "version": "0.5.0p74",
+ "version": "0.5.1p74",
"source": {
"type": "git",
"url": "https://git.univ-reunion.fr/sda-php/nulib.git",
- "reference": "4037bf20424eb48708e5fdf9fc8e10f2ef71d134"
+ "reference": "80943463586f501b033a90a1a28df857b2c67f1c"
},
"require": {
"ext-json": "*",
@@ -629,7 +629,7 @@
}
],
"description": "fonctions et classes essentielles",
- "time": "2025-04-30T00:32:10+00:00"
+ "time": "2025-05-12T11:28:47+00:00"
},
{
"name": "nulib/phpss",
@@ -1310,16 +1310,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "9.6.22",
+ "version": "9.6.23",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c"
+ "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
- "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95",
+ "reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95",
"shasum": ""
},
"require": {
@@ -1330,7 +1330,7 @@
"ext-mbstring": "*",
"ext-xml": "*",
"ext-xmlwriter": "*",
- "myclabs/deep-copy": "^1.12.1",
+ "myclabs/deep-copy": "^1.13.1",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=7.3",
@@ -1393,7 +1393,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23"
},
"funding": [
{
@@ -1404,12 +1404,20 @@
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
+ {
+ "url": "https://liberapay.com/sebastianbergmann",
+ "type": "liberapay"
+ },
+ {
+ "url": "https://thanks.dev/u/gh/sebastianbergmann",
+ "type": "thanks_dev"
+ },
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
"type": "tidelift"
}
],
- "time": "2024-12-05T13:48:26+00:00"
+ "time": "2025-05-02T06:40:34+00:00"
},
{
"name": "psr/http-client",
@@ -2654,7 +2662,7 @@
},
{
"name": "symfony/polyfill-ctype",
- "version": "v1.31.0",
+ "version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
@@ -2713,7 +2721,7 @@
"portable"
],
"support": {
- "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0"
},
"funding": [
{
@@ -2733,19 +2741,20 @@
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.31.0",
+ "version": "v1.32.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
- "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
+ "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
"shasum": ""
},
"require": {
+ "ext-iconv": "*",
"php": ">=7.2"
},
"provide": {
@@ -2793,7 +2802,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0"
},
"funding": [
{
@@ -2809,7 +2818,7 @@
"type": "tidelift"
}
],
- "time": "2024-09-09T11:45:10+00:00"
+ "time": "2024-12-23T08:48:59+00:00"
},
{
"name": "symfony/yaml",
diff --git a/nur_bin/sqlite-storage.php b/nur_bin/sqlite-storage.php
index d81b72f..a636ce3 100755
--- a/nur_bin/sqlite-storage.php
+++ b/nur_bin/sqlite-storage.php
@@ -63,21 +63,14 @@ Application::run(new class extends Application {
$storage = new SqliteStorage($dbfile);
$db = $storage->db();
- $haveChannels = $storage->tableExists("_channels");
-
$name = $this->name;
$channelClass = $this->channelClass;
$tableName = $this->tableName;
if ($name !== null) {
- $row = null;
- if ($haveChannels) {
- $row = $db->one([
- "select from _channels",
- "where" => ["name" => $name],
- ]);
+ if (!$storage->channelExists($name, $row)) {
+ self::die("$name: nom de canal de données introuvable");
}
- if ($row === null) self::die("$name: nom de canal de données introuvable");
- if ($row["class"] !== "class@anonymous") $channelClass = $row["class"];
+ if ($row["class_name"] !== "class@anonymous") $channelClass = $row["class_name"];
else $tableName = $row["table_name"];
}
if ($channelClass !== null) {
@@ -92,17 +85,12 @@ Application::run(new class extends Application {
};
} else {
$found = false;
- if ($haveChannels) {
- $rows = $db->all([
- "select from _channels",
- ]);
- foreach ($rows as $row) {
- msg::print($row["name"]);
- $found = true;
- }
+ foreach ($storage->getChannels() as $row) {
+ msg::print($row["name"]);
+ $found = true;
}
- if (!$found) self::die("Vous devez spécifier le canal de données");
- else self::exit();
+ if ($found) self::exit();
+ self::die("Vous devez spécifier le canal de données");
}
$capacitor = new Capacitor($storage, $channel);
diff --git a/nur_public/nur-v-bs3/base.css b/nur_public/nur-v-bs3/base.css
index 890de9a..c13ccf3 100644
--- a/nur_public/nur-v-bs3/base.css
+++ b/nur_public/nur-v-bs3/base.css
@@ -34,6 +34,12 @@
.left-gap { margin-left: 1em;}
.right-gap { margin-right: 1em;}
+table.table-sticky tr th {
+ position: sticky;
+ top: 0;
+ background-color: white;
+}
+
/* si un navbar-form contient des btn-sm ou des btn-xs, utiliser les classes ci-dessous. */
.navbar-form-sm { margin-top: 10px; margin-bottom: 10px; }
.navbar-form-xs { margin-top: 14px; margin-bottom: 14px; }
diff --git a/nur_src/b/authnz/AuthzManager.php b/nur_src/b/authnz/AuthzManager.php
index 12b8129..f188bc2 100644
--- a/nur_src/b/authnz/AuthzManager.php
+++ b/nur_src/b/authnz/AuthzManager.php
@@ -1,12 +1,22 @@
getCookieKey(), false);
if ($value === false) return false;
- if (!preg_match('/^(cas|form|):(.*)$/', $value, $ms)) return false;
+ if (!preg_match('/^(cas|form|ext|):(.*)$/', $value, $ms)) return false;
$authType = $ms[1];
$username = $ms[2];
return true;
@@ -191,6 +201,22 @@ class AuthzManager {
page::redirect(page::bu($loginUrl, $params));
}
+ function extLogin(string $username): bool {
+ # l'utilisateur doit exister
+ $user = $this->getUserManager()->getAuthzUser($username, null);
+ # ce doit être un utilisateur valide
+ if ($user !== null && $user->isValid()) {
+ $this->setCookie($username, "ext");
+ $this->initSession(self::STATUS_INITIAL);
+ session::set(self::SESSION_KEY_USERNAME, $username);
+ $this->onAuthOk($username);
+ session::set(self::SESSION_KEY_USER, $user);
+ $this->onAuthzOk($user);
+ return true;
+ }
+ return false;
+ }
+
function formLogin(string $username, string $password): bool {
# l'utilisateur doit exister
$user = $this->getUserManager()->getAuthzUser($username, null);
@@ -202,7 +228,7 @@ class AuthzManager {
if (config::is_devel() && !$password) $password = null;
if ($password === null || $user->validatePassword($password)) {
# c'est bon
- $this->initSession(self::STATUS_INITIAL, null);
+ $this->initSession(self::STATUS_INITIAL);
session::set(self::SESSION_KEY_USERNAME, $username);
$this->onAuthOk($username);
session::set(self::SESSION_KEY_USER, $user);
@@ -221,14 +247,11 @@ class AuthzManager {
$this->onAuthOk($username);
# l'utilisateur doit exister
$user = $this->getUserManager()->getAuthzUser($username, $overrides);
- if ($user !== null) {
- # ce doit être un utilisteur valide
- if ($user->isValid()) {
- # c'est bon
- session::set(self::SESSION_KEY_USER, $user);
- $this->onAuthzOk($user);
- return true;
- }
+ # ce doit être un utilisteur valide
+ if ($user !== null && $user->isValid()) {
+ session::set(self::SESSION_KEY_USER, $user);
+ $this->onAuthzOk($user);
+ return true;
}
return false;
}
@@ -317,4 +340,83 @@ class AuthzManager {
/** Traiter le cas où l'utilisateur a été autorisé avec succès. */
function onAuthzOk(IAuthzUser $authz): void {
}
+
+ #############################################################################
+ # Page login
+
+ private $username = null;
+ private $authType = null;
+
+ function beforeSetup(AInitAuthzPage $page): void {
+ # initialiser la session avant setup. ainsi, dans les fonction beforeSetup(),
+ # setup() et afterSetup(), la session est disponible
+ $username = P::get("u");
+ $password = P::get("p");
+ $destPage = F::get("d", $page->getMainUrl());
+ $page->_ensureFormLoginAndRedirect($username, $password, $destPage);
+
+ $this->checkSession($this->username, $this->authType);
+ }
+
+ function print(AInitAuthzPage $page): void {
+ page::no_cache();
+ $username = P::get("u");
+ $password = P::get("p");
+
+ ly::row();
+ vo::h1(["class" => "text-center", q($page->TITLE())]);
+
+ ly::col(["sm" => 6, "sm-push" => 3]);
+ $status = $this->getStatus();
+ switch ($status) {
+ case authz::DISCONNECTED:
+ msg::warning("Vous avez été déconnecté. Veuillez vous reconnecter");
+ break;
+ case authz::UNAUTHORIZED:
+ msg::error(["user" => [
+ "Connecté en tant que ",
+ v::b($this->getAuth()),
+ ", vous n'êtes pas autorisé à accéder à la page que vous avez demandé.",
+ ]]);
+ break;
+ }
+
+ ly::panel("Connexion par identifiant/mot de passe");
+ fo::start([
+ "type" => "basic",
+ "action" => "",
+ "method" => "post",
+ ]);
+ fo::text("Identifiant", "u", $username?: $this->username, [
+ "accesskey" => "q",
+ "placeholder" => "Votre identifiant",
+ ]);
+ fo::password("Mot de passe", "p", $password, [
+ "placeholder" => "Votre mot de passe",
+ ]);
+ if ($username || $password) {
+ msg::error("$username: Votre identifiant et/ou votre mot de passe sont incorrects");
+ } elseif ($username === "") {
+ msg::error("Vous devez saisir votre identifiant");
+ } elseif ($password === "") {
+ msg::error("Vous devez saisir votre mot de passe");
+ }
+ fo::submit(["Connexion", "accesskey" => "r"]);
+ if ($this->isAuth() && $this->authType === "form") {
+ if ($status != authz::UNAUTHORIZED) {
+ msg::warning(["user" => [
+ "Connecté en tant que ",
+ v::b($this->getAuth()),
+ ", vous n'êtes pas autorisé à accéder à cette application.",
+ ]]);
+ }
+ fo::submit([
+ "Vous déconnecter", "accesskey" => "z",
+ "formmethod" => "get", "formaction" => $page->getLogoutUrl(),
+ ]);
+ }
+ fo::end();
+
+ ly::end();
+ }
}
diff --git a/nur_src/b/authnz/CasAuthzManager.php b/nur_src/b/authnz/CasAuthzManager.php
index da544e0..ec02b97 100644
--- a/nur_src/b/authnz/CasAuthzManager.php
+++ b/nur_src/b/authnz/CasAuthzManager.php
@@ -1,8 +1,18 @@
userManager;
}
+
+ private $destPage = null;
+
+ function beforeSetup(AInitAuthzPage $page): void {
+ # initialiser la session avant setup. ainsi, dans les fonction beforeSetup(),
+ # setup() et afterSetup(), la session est disponible
+ $this->destPage = F::get("d", $page->getMainUrl());
+ $this->checkSession($username, $authType);
+
+ if ($authType === "cas" && F::get("a")) {
+ # autologin
+ $casauthUrl = config::k("url")."/".$page->getCasauthUrl();
+ page::redirect(page::bu($page->getCasLoginUrl(), [
+ "service" => page::bu($casauthUrl, [
+ "r" => $page->getLoginUrl(),
+ "d" => $this->destPage,
+ ])
+ ]));
+ }
+ }
+
+ function print(AInitAuthzPage $page): void {
+ page::no_cache();
+ ly::row();
+ vo::h1(["class" => "text-center", q($page->TITLE())]);
+
+ ly::col(["sm" => 6, "sm-push" => 3]);
+ $status = $this->getStatus();
+ switch ($status) {
+ case authz::DISCONNECTED:
+ msg::warning("Vous avez été déconnecté. Veuillez vous reconnecter");
+ break;
+ case authz::UNAUTHORIZED:
+ msg::error(["user" => [
+ "Connecté en tant que ",
+ v::b($this->getAuth()),
+ ", vous n'êtes pas autorisé à accéder à la page que vous avez demandé.",
+ ]]);
+ break;
+ }
+
+ ly::panel("Connexion par CAS");
+ if ($page->isDevauthAllowed()) {
+ fo::start([
+ "type" => "basic",
+ "action" => $page->getCasauthUrl(),
+ "method" => "get",
+ ]);
+ fo::hidden("r", $page->getLoginUrl());
+ fo::hidden("d", $this->destPage);
+ } else {
+ fo::start([
+ "type" => "basic",
+ "action" => $page->getCasLoginUrl(),
+ "method" => "get",
+ ]);
+ $casauthUrl = config::k("url")."/".$page->getCasauthUrl();
+ fo::hidden("service", page::bu($casauthUrl, [
+ "r" => $page->getLoginUrl(),
+ "d" => $this->destPage,
+ ]));
+ }
+ #fo::p("Si vous avez un compte à l'université, vous pouvez vous connecter via CAS");
+ vo::p("Si vous avez un compte à l'université, vous pouvez vous connecter via CAS");
+ if ($this->isAuth()) {
+ if ($status != authz::UNAUTHORIZED) {
+ msg::warning(["user" => [
+ "Connecté en tant que ",
+ v::b(authz::get_auth()),
+ ", vous n'êtes pas autorisé à accéder à cette application.",
+ ]]);
+ }
+ fo::submit([
+ icon::logout("Vous déconnecter"),
+ "formaction" => $page->getLogoutUrl(),
+ "accesskey" => "z",
+ ]);
+ fo::hidden("renew", "true");
+ fo::submit([
+ icon::login("Changer de compte"),
+ "accesskey" => "r",
+ ]);
+ } else {
+ fo::submit(["Connexion par CAS", "accesskey" => "r"]);
+ }
+ fo::end();
+
+ ly::end();
+ }
}
diff --git a/nur_src/b/authnz/ExtAuthzManager.php b/nur_src/b/authnz/ExtAuthzManager.php
new file mode 100644
index 0000000..20e28d3
--- /dev/null
+++ b/nur_src/b/authnz/ExtAuthzManager.php
@@ -0,0 +1,78 @@
+destPage = F::get("d", $page->getMainUrl());
+ $this->checkSession($username, $authType);
+
+ if ($authType === "ext" && F::get("a")) {
+ # autologin
+ $extauthUrl = config::k("url")."/".$page->getExtauthUrl();
+ page::redirect(page::bu($extauthUrl, [
+ "d" => $this->destPage,
+ ]));
+ }
+ }
+
+ function print(AInitAuthzPage $page): void {
+ page::no_cache();
+ ly::row();
+ vo::h1(["class" => "text-center", q($page->TITLE())]);
+
+ ly::col(["sm" => 6, "sm-push" => 3]);
+ $status = $this->getStatus();
+ switch ($status) {
+ case authz::DISCONNECTED:
+ msg::warning("Vous avez été déconnecté. Veuillez vous reconnecter");
+ break;
+ case authz::UNAUTHORIZED:
+ msg::error(["user" => [
+ "Connecté en tant que ",
+ v::b($this->getAuth()),
+ ", vous n'êtes pas autorisé à accéder à la page que vous avez demandé.",
+ ]]);
+ break;
+ }
+
+ ly::panel("Connexion");
+ fo::start([
+ "type" => "basic",
+ "action" => $page->getExtauthUrl(),
+ "method" => "get",
+ ]);
+ fo::hidden("d", $this->destPage);
+ vo::p("Si vous avez un compte à l'université, vous pouvez vous connecter");
+ if ($this->isAuth()) {
+ if ($status != authz::UNAUTHORIZED) {
+ msg::warning(["user" => [
+ "Connecté en tant que ",
+ v::b($this->getAuth()),
+ ", vous n'êtes pas autorisé à accéder à cette application.",
+ ]]);
+ }
+ } else {
+ fo::submit(["Connexion", "accesskey" => "r"]);
+ }
+ fo::end();
+
+ ly::end();
+ }
+}
diff --git a/nur_src/b/authnz/ExtUserManager.php b/nur_src/b/authnz/ExtUserManager.php
new file mode 100644
index 0000000..4ca8f19
--- /dev/null
+++ b/nur_src/b/authnz/ExtUserManager.php
@@ -0,0 +1,8 @@
+ $username];
+ }
+}
diff --git a/nur_src/v/bs3/vc/CTable.php b/nur_src/v/bs3/vc/CTable.php
index 226c2f9..1ff1716 100644
--- a/nur_src/v/bs3/vc/CTable.php
+++ b/nur_src/v/bs3/vc/CTable.php
@@ -633,14 +633,15 @@ class CTable extends ComponentPrintable implements IParametrable {
/** @var string|int clé de la colonne courante */
protected $col;
- function colTd($value): array {
+ function colTd($value): ?array {
$vs = $this->col($value);
if ($this->colCtx !== null) {
$vs = func::_call($this->colCtx, [$vs, $value, $this->col, $this->index, $this->row, $this->rawRow]);
+ if ($vs === false) return null;
} else {
$result = A::get($this->results, $this->col);
$valid = $result === null || $result["valid"];
- $vs= [
+ $vs = [
"class" => ["danger" => !$valid],
$vs,
];
diff --git a/nur_src/v/vp/AInitAuthzPage.php b/nur_src/v/vp/AInitAuthzPage.php
index f316bba..bdafa02 100644
--- a/nur_src/v/vp/AInitAuthzPage.php
+++ b/nur_src/v/vp/AInitAuthzPage.php
@@ -64,7 +64,7 @@ class AInitAuthzPage extends AInitPage implements IBasicPage {
}
}
- protected function ensureAuthOrRedirect(bool $requireAuth, bool $requireAuthz, $requireRole, $requirePerm): void {
+ protected function _ensureAuthOrRedirect(bool $requireAuth, bool $requireAuthz, $requireRole, $requirePerm): void {
$am = authz::manager();
$loginUrl = $this->getLoginUrl();
$destUrl = page::self(true);
@@ -88,7 +88,7 @@ class AInitAuthzPage extends AInitPage implements IBasicPage {
if ($am->isAuth()) $am->setConnected();
}
- protected function ensureFormLoginAndRedirect(?string $username, ?string $password, string $destUrl): void {
+ function _ensureFormLoginAndRedirect(?string $username, ?string $password, string $destUrl): void {
if ($username === null && $password === null) return;
if (authz::manager()->formLogin($username, $password)) {
page::redirect($destUrl);
diff --git a/nur_src/v/vp/AInitPage.php b/nur_src/v/vp/AInitPage.php
index 08fd920..e497785 100644
--- a/nur_src/v/vp/AInitPage.php
+++ b/nur_src/v/vp/AInitPage.php
@@ -40,6 +40,11 @@ abstract class AInitPage extends AbstractPage {
}
}
+ /** obtenir l'url d'authentification externe. cet url doit être *relatif* */
+ function getExtauthUrl(): string {
+ return page::bu(config::k("extauth_page", "_extauth.php"));
+ }
+
function getLogoutUrl(): string {
return page::bu(config::k("logout_page","_logout.php"));
}
diff --git a/nur_src/v/vp/AppExtauthPage.php b/nur_src/v/vp/AppExtauthPage.php
new file mode 100644
index 0000000..75a4653
--- /dev/null
+++ b/nur_src/v/vp/AppExtauthPage.php
@@ -0,0 +1,68 @@
+extLogin($user)) {
+ $destUrl = F::get("d");
+ if ($destUrl && !$this->isDebug()) page::redirect($destUrl);
+ }
+ } else {
+ $user = "NONE";
+ }
+
+ $this->user = $user;
+ $this->destUrl = $destUrl;
+ }
+
+ private $destUrl;
+
+ function print(): void {
+ page::content_type("text/plain");
+ page::no_cache();
+ if ($this->isDebug()) {
+ echo "destUrl: $this->destUrl\n";
+ echo "--- \$_SERVER\n\n";
+ foreach ($_SERVER as $name => $value) {
+ echo "$name: $value\n";
+ }
+ echo "\n--- \$_REQUEST\n\n";
+ foreach ($_REQUEST as $name => $value) {
+ echo "$name: $value\n";
+ }
+ echo "\n--- \$_SESSION\n\n";
+ foreach ($_SESSION as $name => $value) {
+ echo "$name: ".var_export($value, true)."\n";
+ }
+ } else {
+ echo $this->user;
+ }
+ }
+}
diff --git a/nur_src/v/vp/BasicPage.php b/nur_src/v/vp/BasicPage.php
index 6eb4ef6..c83ca4a 100644
--- a/nur_src/v/vp/BasicPage.php
+++ b/nur_src/v/vp/BasicPage.php
@@ -13,7 +13,7 @@ class BasicPage extends AInitAuthzPage {
function afterConfig(): void {
# initialiser la session avant setup. ainsi, dans les fonction beforeSetup(),
# setup() et afterSetup(), la session est disponible
- $this->ensureAuthOrRedirect(static::REQUIRE_AUTH, static::REQUIRE_AUTHZ, static::REQUIRE_ROLE, static::REQUIRE_PERM);
+ $this->_ensureAuthOrRedirect(static::REQUIRE_AUTH, static::REQUIRE_AUTHZ, static::REQUIRE_ROLE, static::REQUIRE_PERM);
parent::afterConfig();
}
}
diff --git a/nur_src/v/vp/NavigablePage.php b/nur_src/v/vp/NavigablePage.php
index 23212c3..5225b6f 100644
--- a/nur_src/v/vp/NavigablePage.php
+++ b/nur_src/v/vp/NavigablePage.php
@@ -32,7 +32,7 @@ class NavigablePage extends AInitAuthzPage implements INavigablePage {
function afterConfig(): void {
# initialiser la session avant setup. ainsi, dans les fonction beforeSetup(),
# setup() et afterSetup(), la session est disponible
- $this->ensureAuthOrRedirect(static::REQUIRE_AUTH, static::REQUIRE_AUTHZ, static::REQUIRE_ROLE, static::REQUIRE_PERM);
+ $this->_ensureAuthOrRedirect(static::REQUIRE_AUTH, static::REQUIRE_AUTHZ, static::REQUIRE_ROLE, static::REQUIRE_PERM);
parent::afterConfig();
}
diff --git a/nur_src/v/vp/TAuthzLoginPage.php b/nur_src/v/vp/TAuthzLoginPage.php
new file mode 100644
index 0000000..5517e56
--- /dev/null
+++ b/nur_src/v/vp/TAuthzLoginPage.php
@@ -0,0 +1,19 @@
+beforeSetup($this);
+ }
+
+ function beforeSetup(): void {
+ $this->TLoginPage_beforeSetup();
+ parent::beforeSetup();
+ }
+
+ function print(): void {
+ authz::manager()->print($this);
+ }
+}
diff --git a/nur_src/v/vp/TFormLoginPage.php b/nur_src/v/vp/TFormLoginPage.php
index 088f1a7..387f776 100644
--- a/nur_src/v/vp/TFormLoginPage.php
+++ b/nur_src/v/vp/TFormLoginPage.php
@@ -17,7 +17,7 @@ trait TFormLoginPage {
$username = P::get("u");
$password = P::get("p");
$destPage = F::get("d", $this->getMainUrl());
- $this->ensureFormLoginAndRedirect($username, $password, $destPage);
+ $this->_ensureFormLoginAndRedirect($username, $password, $destPage);
authz::manager()->checkSession($flcUsername, $flcAuthType);
$this->flcUsername = $flcUsername;
diff --git a/src/php/access/KeyAccess.php b/src/php/access/KeyAccess.php
index 9afd1a2..1f445ca 100644
--- a/src/php/access/KeyAccess.php
+++ b/src/php/access/KeyAccess.php
@@ -2,6 +2,7 @@
namespace nulib\php\access;
use ArrayAccess;
+use nulib\A;
use nulib\cl;
/**
@@ -118,85 +119,18 @@ class KeyAccess extends AbstractAccess {
}
function ensureAssoc(array $keys, ?array $params=null): void {
- $dest =& $this->dest;
- $prefix = $params["key_prefix"] ?? null;
- $suffix = $params["key_suffix"] ?? null;
- $index = 0;
- foreach ($keys as $key) {
- if ($prefix !== null || $suffix !== null) {
- $destKey = "$prefix$key$suffix";
- } else {
- # préserver les clés numériques
- $destKey = $key;
- }
- if ($dest !== null && array_key_exists($destKey, $dest)) continue;
- while (in_array($index, $keys, true)) {
- $index++;
- }
- if ($dest !== null && array_key_exists($index, $dest)) {
- $dest[$destKey] = $dest[$index];
- unset($dest[$index]);
- $index++;
- }
- }
+ A::ensure_assoc($this->dest, $keys, $params);
}
function ensureKeys(array $defaults, ?array $missings, ?array $params=null): void {
- $dest =& $this->dest;
- $keys = array_keys($defaults);
- $prefix = $params["key_prefix"] ?? null;
- $suffix = $params["key_suffix"] ?? null;
- foreach ($keys as $key) {
- $destKey = "$prefix$key$suffix";
- $haveMissing = $missings !== null && array_key_exists($key, $missings);
- if ($dest === null || !array_key_exists($destKey, $dest)) {
- $dest[$destKey] = $defaults[$key];
- } elseif ($haveMissing && $dest[$destKey] === $missings[$key]) {
- $dest[$destKey] = $defaults[$key];
- }
- }
+ A::ensure_keys($this->dest, $defaults, $missings, $params);
}
function deleteMissings(array $missings, ?array $params=null): void {
- $dest =& $this->dest;
- $prefix = $params["key_prefix"] ?? null;
- $suffix = $params["key_suffix"] ?? null;
- foreach ($missings as $key => $missing) {
- $destKey = "$prefix$key$suffix";
- if (array_key_exists($destKey, $dest) && $dest[$destKey] === $missing) {
- unset($dest[$destKey]);
- }
- }
+ A::delete_missings($this->dest, $missings, $params);
}
function ensureOrder(array $keys, ?array $params=null): void {
- $dest =& $this->dest;
- if ($dest === null) return;
-
- $prefix = $params["key_prefix"] ?? null;
- $suffix = $params["key_suffix"] ?? null;
- if ($prefix !== null || $suffix !== null) {
- foreach ($keys as &$key) {
- $key = "$prefix$key$suffix";
- }; unset($key);
- }
-
- $destKeys = array_keys($dest);
- $keyCount = count($keys);
- if (array_slice($destKeys, 0, $keyCount) === $keys) {
- # si le tableau a déjà les bonnes clés dans le bon ordre, rien à faire
- return;
- }
-
- $ordered = [];
- foreach ($keys as $key) {
- if (array_key_exists($key, $dest)) {
- $ordered[$key] = $dest[$key];
- unset($dest[$key]);
- }
- }
- $preserveKeys = $params["preserve_keys"] ?? false;
- if ($preserveKeys) $dest = cl::merge2($ordered, $dest);
- else $dest = array_merge($ordered, $dest);
+ A::ensure_order($this->dest, $keys, $params);
}
}
diff --git a/src/php/coll/Cursor.php b/src/php/coll/Cursor.php
index 4834b50..f9a2bdb 100644
--- a/src/php/coll/Cursor.php
+++ b/src/php/coll/Cursor.php
@@ -11,6 +11,10 @@ use Traversable;
/**
* Class Cursor: parcours des lignes itérable
*
+ * XXX si on spécifie $cols ou $colsFunc, il y a une possibilité que les clés de
+ * $row ne soient pas dans le bon ordre, ou que les clés de $cols ne soient pas
+ * présentes dans $row. ajouter les paramètres ensure_keys et order_keys
+ *
* @property-read array|null $value alias pour $row
* @property-read iterable|null $rows la source des lignes
*/
@@ -18,80 +22,14 @@ class Cursor implements Iterator {
const PARAMS_SCHEMA = [
"rows" => ["?iterable"],
"rows_func" => ["?callable"],
- "cols" => ["?array"],
- "cols_func" => ["?callable"],
- "map" => ["?array"],
- "map_func" => ["?callable"],
"filter" => ["?array"],
"filter_func" => ["?callable"],
+ "map" => ["?array"],
+ "map_func" => ["?callable"],
+ "cols" => ["?array"],
+ "cols_func" => ["?callable"],
];
- /**
- * mapper le tableau source $row selon les règles suivantes illustrées dans
- * l'exemple suivant:
- * si
- * $map = ["a", "b" => "x", "c" => function() { return "y"; }, "d" => null]
- * alors retourner le tableau
- * ["a" => $row["a"], "b" => $row["x"], "c" => "y", "d" => null]
- */
- protected static function map_row(array $row, ?array $map): array {
- if ($map === null) return $row;
- $index = 0;
- $mapped = [];
- foreach ($map as $key => $value) {
- if ($key === $index) {
- $index++;
- if ($value === null) $mapped[] = null;
- else $mapped[$value] = cl::get($row, $value);
- } elseif (is_callable($value)) {
- $func = func::with($value);
- $value = cl::get($row, $key);
- $mapped[$key] = $func->invoke([$value, $key, $row]);
- } else {
- if ($value === null) $mapped[$key] = null;
- else $mapped[$key] = cl::get($row, $value);
- }
- }
- return $mapped;
- }
-
- /**
- * tester si $row satisfait les conditions de $filter
- * - $filter est un scalaire, le transformer en [$filter]
- * - sinon $filter doit être un tableau de scalaires
- *
- * les règles des conditions sont les suivantes:
- * - une valeur séquentielle $key est équivalente à la valeur associative
- * $key => true
- * - une valeur associative $key => bool indique que la clé correspondante ne
- * doit pas (resp. doit) exister selon que bool vaut false (resp. true)
- * - une valeur associative $key => $value indique que la clé correspondante
- * doit exiter avec la valeur spécifiée
- */
- protected static function filter_row(array $row, $filter): bool {
- if ($filter === null) return false;
- if (!is_array($filter)) $filter = [$filter];
- if (!$filter) return false;
-
- $index = 0;
- foreach ($filter as $key => $value) {
- if ($key === $index) {
- $index++;
- if (!array_key_exists($value, $row)) return false;
- } elseif (is_bool($value)) {
- if ($value) {
- if (!array_key_exists($key, $row)) return false;
- } else {
- if (array_key_exists($key, $row)) return false;
- }
- } else {
- if (!array_key_exists($key, $row)) return false;
- if ($row[$key] !== $value) return false;
- }
- }
- return true;
- }
-
function __construct(?iterable $rows=null, ?array $params=null) {
if ($rows !== null) $params["rows"] = $rows;
@@ -103,7 +41,7 @@ class Cursor implements Iterator {
$rowsGenerator = $rowsFunc;
$rowsFunc = null;
} else {
- $rowsFunc = func::with($rowsFunc, [$rows]);
+ $rowsFunc = func::with($rowsFunc, [$rows, $this]);
}
} elseif ($rows instanceof Traversable) {
$rowsGenerator = $rows;
@@ -115,61 +53,95 @@ class Cursor implements Iterator {
$this->rowsGenerator = $rowsGenerator;
$this->rowsFunc = $rowsFunc;
- $this->cols = $params["cols"] ?? null;
- $colsFunc = $params["cols_func"] ?? null;
- if ($colsFunc !== null) $colsFunc = func::with($colsFunc);
- $this->colsFunc = $colsFunc;
+ $filter = $params["filter"] ?? null;
+ $filterFunc = $params["filter_func"] ?? null;
+ if ($filterFunc !== null) $this->setFilterFunc($filterFunc);
+ elseif ($filter !== null) $this->setFilter($filter);
$map = $params["map"] ?? null;
$mapFunc = $params["map_func"] ?? null;
- if ($mapFunc !== null) {
- $mapFunc = func::with($mapFunc);
- } elseif ($map !== null) {
- $mapFunc = func::with(function(array $row) use ($map) {
- return self::map_row($row, $map);
- });
- }
- $this->mapFunc = $mapFunc;
+ if ($mapFunc !== null) $this->setMapFunc($mapFunc);
+ elseif ($map !== null) $this->setMap($map);
- $filter = $params["filter"] ?? null;
- $filterFunc = $params["filter_func"] ?? null;
- if ($filterFunc !== null) {
- $filterFunc = func::with($filterFunc);
- } elseif ($filter !== null) {
- $filterFunc = func::with(function(array $row) use ($filter) {
- return self::filter_row($row, $filter);
- });
- }
- $this->filterFunc = $filterFunc;
+ $this->cols = $params["cols"] ?? null;
+ $this->setColsFunc($params["cols_func"] ?? null);
}
/** un générateur de lignes */
private ?Traversable $rowsGenerator;
- /** une fonction de signature function(Cursor): ?iterable
*/
+ /** une fonction de signature function(mixed $rows, Cursor): ?iterable
*/
private ?func $rowsFunc;
- /** une fonction de signature function(Cursor): ?array
*/
- private ?func $colsFunc;
+ /** une fonction de signature function(?array $row, Cursor): bool
*/
+ private ?func $filterFunc = null;
- /** une fonction de signature function(Cursor): ?array
*/
- private ?func $mapFunc;
+ function setFilter(array $filter): self {
+ $this->filterFunc = func::with(function(?array $row) use ($filter) {
+ return cl::filter($row, $filter);
+ });
+ return $this;
+ }
- /** une fonction de signature function(Cursor): bool
*/
- private ?func $filterFunc;
+ function setFilterFunc(?callable $func): self {
+ if ($func === null) $this->filterFunc = null;
+ else $this->filterFunc = func::with($func)->bind($this);
+ return $this;
+ }
+ /** une fonction de signature function(?array $row, Cursor): ?array
*/
+ private ?func $mapFunc = null;
+
+ function setMap(array $map): self {
+ $this->mapFunc = func::with(function(?array $row) use ($map) {
+ return cl::map($row, $map);
+ });
+ return $this;
+ }
+
+ function setMapFunc(?callable $func): self {
+ if ($func === null) $this->mapFunc = null;
+ else $this->mapFunc = func::with($func)->bind($this);
+ return $this;
+ }
+
+ /** une fonction de signature function(?array $row, Cursor): ?array
*/
+ private ?func $colsFunc = null;
+
+ function setColsFunc(?callable $func): self {
+ $this->cols = null;
+ if ($func === null) $this->colsFunc = null;
+ else $this->colsFunc = func::with($func)->bind($this);
+ return $this;
+ }
+
+ /** @var iterable|null source des éléments */
protected ?iterable $rows;
+ /** @var array|null listes des colonnes de chaque enregistrement */
public ?array $cols;
+ /**
+ * @var int index de l'enregistrement (en ne comptant pas les éléments filtrés)
+ */
public int $index;
+ /**
+ * @var int index original de l'enregistrement (en tenant compte des éléments
+ * filtrés)
+ */
public int $origIndex;
+ /** @var string|int clé de l'enregistrement */
public $key;
+ /** @var mixed élément original récupéré depuis la source */
public $raw;
+ /**
+ * @var array|null enregistrement après conversion en tableau et application
+ * du mapping
+ */
public ?array $row;
function __get($name) {
@@ -179,18 +151,22 @@ class Cursor implements Iterator {
return null;
}
- protected function cols(): ?array {
- return $this->row !== null? array_keys($this->row): null;
+ protected function convertToRow($raw): ?array {
+ return cl::withn($raw);
}
- protected function filter(): bool {
+ protected function filterFunc(?array $row): bool {
return false;
}
- protected function map(): ?array {
+ protected function mapFunc(?array $row): ?array {
return $this->row;
}
+ protected function colsFunc(?array $row): ?array {
+ return $this->row !== null? array_keys($this->row): null;
+ }
+
#############################################################################
# Iterator
@@ -212,26 +188,30 @@ class Cursor implements Iterator {
}
function valid(): bool {
- $cols = $this->colsFunc;
- $filter = $this->filterFunc;
- $map = $this->mapFunc;
+ $colsFunc = $this->colsFunc;
+ $filterFunc = $this->filterFunc;
+ $mapFunc = $this->mapFunc;
while ($valid = iter::valid($this->rows)) {
$this->raw = iter::current($this->rows, $this->key);
+ # conversion en enregistrement
$this->key ??= $this->origIndex;
- $this->row = cl::withn($this->raw);
- if ($filter === null) $filtered = $this->filter();
- else $filtered = $filter->invoke([$this]);
- if (!$filtered) {
- if ($map === null) $this->row = $this->map();
- else $this->row = $map->invoke([$this]);
- if ($this->cols === null) {
- if ($cols === null) $this->cols = $this->cols();
- else $this->cols = $cols->invoke([$this]);
- }
- break;
- } else {
+ $this->row = $this->convertToRow($this->raw);
+ # filtrage
+ if ($filterFunc === null) $filtered = $this->filterFunc($this->row);
+ else $filtered = $filterFunc->invoke([$this->row, $this]);
+ if ($filtered) {
iter::next($this->rows);
$this->origIndex++;
+ } else {
+ # l'enregistrement n'as pas été filtré: faire le mapping
+ if ($mapFunc === null) $this->row = $this->mapFunc($this->row);
+ else $this->row = $mapFunc->invoke([$this->row, $this]);
+ # calculer la liste des colonnes le cas échéant
+ if ($this->cols === null) {
+ if ($colsFunc === null) $this->cols = $this->colsFunc($this->row);
+ else $this->cols = $colsFunc->invoke([$this->row, $this]);
+ }
+ break;
}
}
if (!$valid) {
diff --git a/src/schema/Schema.php b/src/schema/Schema.php
index fbb441b..2b96626 100644
--- a/src/schema/Schema.php
+++ b/src/schema/Schema.php
@@ -127,7 +127,6 @@ abstract class Schema implements ArrayAccess {
}
}
# type
- $types = [];
$deftype = $definition["type"];
$nullable = $definition["nullable"] ?? false;
if ($deftype === null) {
@@ -138,7 +137,17 @@ abstract class Schema implements ArrayAccess {
if (!is_string($deftype)) throw SchemaException::invalid_type($deftype);
$deftype = explode("|", $deftype);
}
- foreach ($deftype as $type) {
+ $types = [];
+ $unionTypes = [];
+ $index = 0;
+ foreach ($deftype as $key => $type) {
+ $args = null;
+ if ($key === $index) {
+ $index++;
+ } else {
+ $args = $type;
+ $type = $key;
+ }
if ($type !== null) $type = trim($type);
if ($type === null || $type === "null") {
$nullable = true;
@@ -151,10 +160,18 @@ abstract class Schema implements ArrayAccess {
}
if ($type === "") throw SchemaException::invalid_type($type);
$type = cl::get(ref_types::ALIASES, $type, $type);
- $types = array_merge($types, explode("|", $type));
+ if ($args === null) {
+ $unionTypes = array_merge($unionTypes, explode("|", $type));
+ } else {
+ $types = array_merge($types, [$type => $args]);
+ }
+ }
+ if (!$types && !$unionTypes) throw SchemaException::invalid_schema("scalar: type is required");
+ foreach ($unionTypes as $type) {
+ if (!array_key_exists($type, $types)) {
+ $types[] = $type;
+ }
}
- if (!$types) throw SchemaException::invalid_schema("scalar: type is required");
- $types = array_keys(array_fill_keys($types, true));
}
$definition["type"] = $types;
$definition["nullable"] = $nullable;
@@ -224,7 +241,7 @@ abstract class Schema implements ArrayAccess {
$types = $definition["type"];
$nullable = $definition["nullable"];
# s'il n'y a qu'une seul type, l'instancier tout de suite
- if (is_array($types) && count($types) == 1 && $types[0] !== null) {
+ if (is_array($types) && count($types) == 1 && cl::first($types) !== null) {
foreach ($types as $key => $name) {
if ($key === 0) {
$args = null;
diff --git a/src/schema/types/tarray.php b/src/schema/types/tarray.php
index 05cfe7c..d53cddc 100644
--- a/src/schema/types/tarray.php
+++ b/src/schema/types/tarray.php
@@ -5,19 +5,45 @@ use nulib\cl;
use nulib\schema\_scalar\ScalarSchema;
use nulib\schema\Result;
use nulib\schema\Schema;
+use nulib\str;
class tarray extends _tstring {
const NAME = "array";
- const SPLIT_PATTERN = '/\s+/';
- const FORMAT = " ";
+ const TRIM = true;
+ const NORM_NL = true;
+
+ const SEP_MAP = [
+ "space" => "spaces",
+ "lines" => "line",
+ ];
+ const STD_SEPS = [
+ "spaces" => [" ", '/\s+/', true],
+ "line" => ["\n", "\n", false],
+ ];
+
+ const DEFAULT_SEP = self::STD_SEPS["spaces"];
+ const DEFAULT_PARSE_SEP = self::DEFAULT_SEP[1];
+ const DEFAULT_FORMAT_SEP = self::DEFAULT_SEP[0];
public static function get_params_from_definition(?array $definition): ?array {
$params = parent::get_params_from_definition($definition);
- $splitPattern = $definition["split_pattern"] ?? null;
- if ($splitPattern !== null) $params["split_pattern"] = $splitPattern;
- $format = $definition["format"] ?? null;
- if ($format !== null) $params["format"] = $format;
+ $sep = $definition["sep"] ?? null;
+ if ($sep !== null) {
+ if (!is_array($sep)) {
+ $sep = cl::get(self::SEP_MAP, $sep, $sep);
+ $sep = self::STD_SEPS[$sep] ?? [$sep, $sep, false];
+ }
+ $params["parse_sep"] = $sep[1] ?? $sep[0];
+ $params["format_sep"] = $sep[0];
+ $params["trim"] ??= $sep[2] ?? static::TRIM;
+ $params["norm_nl"] ??= $sep[3] ?? static::NORM_NL;
+ } else {
+ $parseSep = $definition["parse_sep"] ?? null;
+ if ($parseSep !== null) $params["parse_sep"] = $parseSep;
+ $formatSep = $definition["format_sep"] ?? null;
+ if ($formatSep !== null) $params["format_sep"] = $formatSep;
+ }
return $params;
}
@@ -43,8 +69,9 @@ class tarray extends _tstring {
}
function parse(string $value) {
- $pattern = $this->params["split_pattern"] ?? static::SPLIT_PATTERN;
- return preg_split($pattern, $value);
+ $sep = $this->params["parse_sep"] ?? static::DEFAULT_PARSE_SEP;
+ if ($sep !== false) $value = str::split($sep, $value);
+ return $value;
}
/**
@@ -63,7 +90,7 @@ class tarray extends _tstring {
function format($value, $format=null): string {
if ($value === null) return "";
- $format ??= $this->params["format"] ?? static::FORMAT;
+ $format ??= $this->params["format"] ?? static::DEFAULT_FORMAT_SEP;
return implode($format, $value);
}
}
diff --git a/tests/php/coll/CursorTest.php b/tests/php/coll/CursorTest.php
index af76c0a..9d9a218 100644
--- a/tests/php/coll/CursorTest.php
+++ b/tests/php/coll/CursorTest.php
@@ -16,7 +16,7 @@ class CursorTest extends TestCase {
function test_map_row() {
$cursor = new class extends Cursor {
function mapRow(array $row, ?array $map): array {
- return self::map_row($row, $map);
+ return cl::map($row, $map);
}
};
$row = ["a" => 1, "b" => 2, "c" => 3, "x" => 99];
@@ -32,7 +32,7 @@ class CursorTest extends TestCase {
function test_filter_row() {
$cursor = new class extends Cursor {
function filterRow(array $row, $filter): bool {
- return self::filter_row($row, $filter);
+ return cl::filter($row, $filter);
}
};
$row = ["a" => 1, "b" => 2, "c" => 3, "x" => 99];
diff --git a/tests/schema/types/arrayTest.php b/tests/schema/types/arrayTest.php
new file mode 100644
index 0000000..95a7e9a
--- /dev/null
+++ b/tests/schema/types/arrayTest.php
@@ -0,0 +1,56 @@
+ "spaces",
+ ]);
+ self::assertSame(["first", "second"], $value);
+
+ $value = " first second ";
+ Schema::nw($value, null, [
+ "array",
+ "sep" => "spaces", "trim" => false,
+ ]);
+ self::assertSame(["", "first", "second", ""], $value);
+
+ $value = " first second ";
+ Schema::nw($value, null, [
+ "array",
+ "sep" => "line",
+ ]);
+ self::assertSame([" first second "], $value);
+
+ $value = " first second ";
+ Schema::nw($value, null, [
+ "array",
+ "sep" => "line", "trim" => true,
+ ]);
+ self::assertSame(["first second"], $value);
+ }
+
+ function testxxx() {
+ $value = " first second ";
+ Schema::nw($value, null, [
+ "array", "sep" => "spaces",
+ ]);
+ self::assertSame(["first", "second"], $value);
+
+ # équivalent à...
+ $value = " first second ";
+ Schema::nw($value, null, [
+ "type" => ["array" => [["sep" => "spaces"]]],
+ "" => ["scalar"],
+ ]);
+ self::assertSame(["first", "second"], $value);
+ }
+}