Compare commits

..

4 Commits

26 changed files with 289 additions and 777 deletions

14
.idea/php.xml generated
View File

@ -21,16 +21,20 @@
<path value="$PROJECT_DIR$/vendor/nulib/phpss" /> <path value="$PROJECT_DIR$/vendor/nulib/phpss" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" /> <path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
<path value="$PROJECT_DIR$/vendor/nulib/spout" /> <path value="$PROJECT_DIR$/vendor/nulib/spout" />
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" /> <path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" /> <path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" /> <path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" /> <path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/ezyang/htmlpurifier" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" /> <path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/myclabs/php-enum" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" /> <path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/maennchen/zipstream-php" /> <path value="$PROJECT_DIR$/vendor/maennchen/zipstream-php" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" /> <path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/markbaker/complex" /> <path value="$PROJECT_DIR$/vendor/markbaker/complex" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" /> <path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" /> <path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" /> <path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" /> <path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
@ -40,6 +44,7 @@
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" /> <path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" /> <path value="$PROJECT_DIR$/vendor/phpoffice/phpspreadsheet" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" /> <path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" /> <path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" /> <path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" /> <path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
@ -59,15 +64,6 @@
<path value="$PROJECT_DIR$/vendor/psr/http-factory" /> <path value="$PROJECT_DIR$/vendor/psr/http-factory" />
<path value="$PROJECT_DIR$/vendor/composer" /> <path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/nulib/spout" /> <path value="$PROJECT_DIR$/vendor/nulib/spout" />
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/ezyang/htmlpurifier" />
<path value="$PROJECT_DIR$/vendor/myclabs/php-enum" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-mbstring" />
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
<path value="$PROJECT_DIR$/vendor/nulib/spout" />
<path value="$PROJECT_DIR$/vendor/nulib/php" /> <path value="$PROJECT_DIR$/vendor/nulib/php" />
<path value="$PROJECT_DIR$/vendor/nulib/spout" /> <path value="$PROJECT_DIR$/vendor/nulib/spout" />
</include_path> </include_path>

View File

@ -1,17 +1,3 @@
## Release 0.5.1p82 du 12/05/2025-15:37
## 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 ## Release 0.5.0p74 du 30/04/2025-05:30
* `2d73f4d` documenter showmorePlugin * `2d73f4d` documenter showmorePlugin

View File

@ -1,51 +0,0 @@
# 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)" &&
echo "commit=$commit"
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

View File

@ -1 +1 @@
0.5.1 0.5.0

View File

@ -3,18 +3,6 @@
"type": "library", "type": "library",
"description": "espace de maturation pour les librairies", "description": "espace de maturation pour les librairies",
"repositories": [ "repositories": [
{
"type": "path",
"url": "../nulib"
},
{
"type": "path",
"url": "../nulib-spout"
},
{
"type": "path",
"url": "../nulib-phpss"
},
{ {
"type": "composer", "type": "composer",
"url": "https://repos.univ-reunion.fr/composer" "url": "https://repos.univ-reunion.fr/composer"
@ -30,9 +18,9 @@
"php": "^7.4" "php": "^7.4"
}, },
"require-dev": { "require-dev": {
"nulib/php": "^7.4-dev", "nulib/php": "^0.5.0p74",
"nulib/spout": "^7.4-dev", "nulib/spout": "^0.5.0p74",
"nulib/phpss": "^7.4-dev", "nulib/phpss": "^0.5.0p74",
"nulib/tests": "^7.4", "nulib/tests": "^7.4",
"ext-posix": "*", "ext-posix": "*",
"ext-pcntl": "*", "ext-pcntl": "*",

93
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "0b1e015d12aecf1cdfbdc6a702d83d25", "content-hash": "b4bd340f94d33a320d66b249b1c21edb",
"packages": [], "packages": [],
"packages-dev": [ "packages-dev": [
{ {
@ -585,11 +585,11 @@
}, },
{ {
"name": "nulib/php", "name": "nulib/php",
"version": "dev-dev74", "version": "0.5.0p74",
"dist": { "source": {
"type": "path", "type": "git",
"url": "../nulib", "url": "https://git.univ-reunion.fr/sda-php/nulib.git",
"reference": "c8dcc6fe279fe3589ca20d13bcbbdc9be2b50285" "reference": "4037bf20424eb48708e5fdf9fc8e10f2ef71d134"
}, },
"require": { "require": {
"ext-json": "*", "ext-json": "*",
@ -629,20 +629,18 @@
} }
], ],
"description": "fonctions et classes essentielles", "description": "fonctions et classes essentielles",
"transport-options": { "time": "2025-04-30T00:32:10+00:00"
"relative": true
}
}, },
{ {
"name": "nulib/phpss", "name": "nulib/phpss",
"version": "dev-dev74", "version": "0.5.0p74",
"dist": { "source": {
"type": "path", "type": "git",
"url": "../nulib-phpss", "url": "https://git.univ-reunion.fr/sda-php/nulib-phpss.git",
"reference": "6fd59afb257e8add4d6b5f524ab2f90a6531c9c7" "reference": "26b4bfddf5646f9313d419e568cd930efb9353eb"
}, },
"require": { "require": {
"nulib/php": "^7.4-dev", "nulib/php": "^0.5.0p74",
"php": "^7.4", "php": "^7.4",
"phpoffice/phpspreadsheet": "^1.0" "phpoffice/phpspreadsheet": "^1.0"
}, },
@ -673,17 +671,15 @@
} }
], ],
"description": "wrapper pour phpoffice/phpspreadsheet", "description": "wrapper pour phpoffice/phpspreadsheet",
"transport-options": { "time": "2025-04-30T00:46:31+00:00"
"relative": true
}
}, },
{ {
"name": "nulib/spout", "name": "nulib/spout",
"version": "dev-dev74", "version": "0.5.0p74",
"dist": { "source": {
"type": "path", "type": "git",
"url": "../nulib-spout", "url": "https://git.univ-reunion.fr/sda-php/nulib-spout.git",
"reference": "242abe737b6cfe186c6d376e904a45a3d3f6f8cd" "reference": "e650e27abe571553424524633deada32747d33a6"
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
@ -691,7 +687,7 @@
"ext-libxml": "*", "ext-libxml": "*",
"ext-xmlreader": "*", "ext-xmlreader": "*",
"ext-zip": "*", "ext-zip": "*",
"nulib/php": "^7.4-dev", "nulib/php": "^0.5.0p74",
"php": "^7.4" "php": "^7.4"
}, },
"replace": { "replace": {
@ -729,9 +725,7 @@
} }
], ],
"description": "wrapper pour openspout/openspout", "description": "wrapper pour openspout/openspout",
"transport-options": { "time": "2025-04-30T00:40:11+00:00"
"relative": true
}
}, },
{ {
"name": "nulib/tests", "name": "nulib/tests",
@ -1316,16 +1310,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "9.6.23", "version": "9.6.22",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95" "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
"reference": "43d2cb18d0675c38bd44982a5d1d88f6d53d8d95", "reference": "f80235cb4d3caa59ae09be3adf1ded27521d1a9c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1336,7 +1330,7 @@
"ext-mbstring": "*", "ext-mbstring": "*",
"ext-xml": "*", "ext-xml": "*",
"ext-xmlwriter": "*", "ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.13.1", "myclabs/deep-copy": "^1.12.1",
"phar-io/manifest": "^2.0.4", "phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1", "phar-io/version": "^3.2.1",
"php": ">=7.3", "php": ">=7.3",
@ -1399,7 +1393,7 @@
"support": { "support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues", "issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy", "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.23" "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.22"
}, },
"funding": [ "funding": [
{ {
@ -1410,20 +1404,12 @@
"url": "https://github.com/sebastianbergmann", "url": "https://github.com/sebastianbergmann",
"type": "github" "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", "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2025-05-02T06:40:34+00:00" "time": "2024-12-05T13:48:26+00:00"
}, },
{ {
"name": "psr/http-client", "name": "psr/http-client",
@ -2668,7 +2654,7 @@
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.32.0", "version": "v1.31.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
@ -2727,7 +2713,7 @@
"portable" "portable"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
}, },
"funding": [ "funding": [
{ {
@ -2747,20 +2733,19 @@
}, },
{ {
"name": "symfony/polyfill-mbstring", "name": "symfony/polyfill-mbstring",
"version": "v1.32.0", "version": "v1.31.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git", "url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-iconv": "*",
"php": ">=7.2" "php": ">=7.2"
}, },
"provide": { "provide": {
@ -2808,7 +2793,7 @@
"shim" "shim"
], ],
"support": { "support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
}, },
"funding": [ "funding": [
{ {
@ -2824,7 +2809,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-12-23T08:48:59+00:00" "time": "2024-09-09T11:45:10+00:00"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
@ -2954,11 +2939,7 @@
], ],
"aliases": [], "aliases": [],
"minimum-stability": "stable", "minimum-stability": "stable",
"stability-flags": { "stability-flags": [],
"nulib/php": 20,
"nulib/spout": 20,
"nulib/phpss": 20
},
"prefer-stable": false, "prefer-stable": false,
"prefer-lowest": false, "prefer-lowest": false,
"platform": { "platform": {

View File

@ -63,14 +63,21 @@ Application::run(new class extends Application {
$storage = new SqliteStorage($dbfile); $storage = new SqliteStorage($dbfile);
$db = $storage->db(); $db = $storage->db();
$haveChannels = $storage->tableExists("_channels");
$name = $this->name; $name = $this->name;
$channelClass = $this->channelClass; $channelClass = $this->channelClass;
$tableName = $this->tableName; $tableName = $this->tableName;
if ($name !== null) { if ($name !== null) {
if (!$storage->channelExists($name, $row)) { $row = null;
self::die("$name: nom de canal de données introuvable"); if ($haveChannels) {
$row = $db->one([
"select from _channels",
"where" => ["name" => $name],
]);
} }
if ($row["class_name"] !== "class@anonymous") $channelClass = $row["class_name"]; if ($row === null) self::die("$name: nom de canal de données introuvable");
if ($row["class"] !== "class@anonymous") $channelClass = $row["class"];
else $tableName = $row["table_name"]; else $tableName = $row["table_name"];
} }
if ($channelClass !== null) { if ($channelClass !== null) {
@ -85,12 +92,17 @@ Application::run(new class extends Application {
}; };
} else { } else {
$found = false; $found = false;
foreach ($storage->getChannels() as $row) { if ($haveChannels) {
$rows = $db->all([
"select from _channels",
]);
foreach ($rows as $row) {
msg::print($row["name"]); msg::print($row["name"]);
$found = true; $found = true;
} }
if ($found) self::exit(); }
self::die("Vous devez spécifier le canal de données"); if (!$found) self::die("Vous devez spécifier le canal de données");
else self::exit();
} }
$capacitor = new Capacitor($storage, $channel); $capacitor = new Capacitor($storage, $channel);

View File

@ -34,12 +34,6 @@
.left-gap { margin-left: 1em;} .left-gap { margin-left: 1em;}
.right-gap { margin-right: 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. */ /* 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-sm { margin-top: 10px; margin-bottom: 10px; }
.navbar-form-xs { margin-top: 14px; margin-bottom: 14px; } .navbar-form-xs { margin-top: 14px; margin-bottom: 14px; }

View File

@ -1,22 +1,12 @@
<?php <?php
namespace nur\b\authnz; namespace nur\b\authnz;
use nur\authz;
use nur\b\IllegalAccessException; use nur\b\IllegalAccessException;
use nur\b\ValueException; use nur\b\ValueException;
use nur\config; use nur\config;
use nur\cookie; use nur\cookie;
use nur\F;
use nur\msg;
use nur\P;
use nur\session; use nur\session;
use nur\v\fo;
use nur\v\ly;
use nur\v\page; use nur\v\page;
use nur\v\v;
use nur\v\vo;
use nur\v\vp\AInitAuthzPage;
use nur\v\vp\AInitPage;
/** /**
* Interface IAuthzManager: gestionnaire d'authentification et d'autorisation * Interface IAuthzManager: gestionnaire d'authentification et d'autorisation
@ -63,7 +53,7 @@ class AuthzManager {
function checkCookie(?string &$username=null, ?string &$authType=null): bool { function checkCookie(?string &$username=null, ?string &$authType=null): bool {
$value = cookie::get($this->getCookieKey(), false); $value = cookie::get($this->getCookieKey(), false);
if ($value === false) return false; if ($value === false) return false;
if (!preg_match('/^(cas|form|ext|):(.*)$/', $value, $ms)) return false; if (!preg_match('/^(cas|form|):(.*)$/', $value, $ms)) return false;
$authType = $ms[1]; $authType = $ms[1];
$username = $ms[2]; $username = $ms[2];
return true; return true;
@ -201,22 +191,6 @@ class AuthzManager {
page::redirect(page::bu($loginUrl, $params)); 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 { function formLogin(string $username, string $password): bool {
# l'utilisateur doit exister # l'utilisateur doit exister
$user = $this->getUserManager()->getAuthzUser($username, null); $user = $this->getUserManager()->getAuthzUser($username, null);
@ -228,7 +202,7 @@ class AuthzManager {
if (config::is_devel() && !$password) $password = null; if (config::is_devel() && !$password) $password = null;
if ($password === null || $user->validatePassword($password)) { if ($password === null || $user->validatePassword($password)) {
# c'est bon # c'est bon
$this->initSession(self::STATUS_INITIAL); $this->initSession(self::STATUS_INITIAL, null);
session::set(self::SESSION_KEY_USERNAME, $username); session::set(self::SESSION_KEY_USERNAME, $username);
$this->onAuthOk($username); $this->onAuthOk($username);
session::set(self::SESSION_KEY_USER, $user); session::set(self::SESSION_KEY_USER, $user);
@ -247,12 +221,15 @@ class AuthzManager {
$this->onAuthOk($username); $this->onAuthOk($username);
# l'utilisateur doit exister # l'utilisateur doit exister
$user = $this->getUserManager()->getAuthzUser($username, $overrides); $user = $this->getUserManager()->getAuthzUser($username, $overrides);
if ($user !== null) {
# ce doit être un utilisteur valide # ce doit être un utilisteur valide
if ($user !== null && $user->isValid()) { if ($user->isValid()) {
# c'est bon
session::set(self::SESSION_KEY_USER, $user); session::set(self::SESSION_KEY_USER, $user);
$this->onAuthzOk($user); $this->onAuthzOk($user);
return true; return true;
} }
}
return false; return false;
} }
@ -340,83 +317,4 @@ class AuthzManager {
/** Traiter le cas où l'utilisateur a été autorisé avec succès. */ /** Traiter le cas où l'utilisateur a été autorisé avec succès. */
function onAuthzOk(IAuthzUser $authz): void { 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();
}
} }

View File

@ -1,18 +1,8 @@
<?php <?php
namespace nur\b\authnz; namespace nur\b\authnz;
use nur\authz;
use nur\config; use nur\config;
use nur\F;
use nur\func; use nur\func;
use nur\msg;
use nur\v\fo;
use nur\v\icon;
use nur\v\ly;
use nur\v\page;
use nur\v\v;
use nur\v\vo;
use nur\v\vp\AInitAuthzPage;
/** /**
* Class CasAuthzManager: un utilisateur authentifié par CAS v3 * Class CasAuthzManager: un utilisateur authentifié par CAS v3
@ -38,93 +28,4 @@ class CasAuthzManager extends AuthzManager {
} }
return $this->userManager; return $this->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();
}
} }

View File

@ -1,78 +0,0 @@
<?php
namespace nur\b\authnz;
use nur\authz;
use nur\config;
use nur\F;
use nur\msg;
use nur\v\fo;
use nur\v\ly;
use nur\v\page;
use nur\v\v;
use nur\v\vo;
use nur\v\vp\AInitAuthzPage;
class ExtAuthzManager extends AuthzManager {
const USER_MANAGER_CLASS = ExtUserManager::class;
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 === "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();
}
}

View File

@ -1,8 +0,0 @@
<?php
namespace nur\b\authnz;
class ExtUserManager extends SimpleUserManager {
function _getUser(string $username): ?array {
return ["username" => $username];
}
}

View File

@ -633,11 +633,10 @@ class CTable extends ComponentPrintable implements IParametrable {
/** @var string|int clé de la colonne courante */ /** @var string|int clé de la colonne courante */
protected $col; protected $col;
function colTd($value): ?array { function colTd($value): array {
$vs = $this->col($value); $vs = $this->col($value);
if ($this->colCtx !== null) { if ($this->colCtx !== null) {
$vs = func::_call($this->colCtx, [$vs, $value, $this->col, $this->index, $this->row, $this->rawRow]); $vs = func::_call($this->colCtx, [$vs, $value, $this->col, $this->index, $this->row, $this->rawRow]);
if ($vs === false) return null;
} else { } else {
$result = A::get($this->results, $this->col); $result = A::get($this->results, $this->col);
$valid = $result === null || $result["valid"]; $valid = $result === null || $result["valid"];

View File

@ -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(); $am = authz::manager();
$loginUrl = $this->getLoginUrl(); $loginUrl = $this->getLoginUrl();
$destUrl = page::self(true); $destUrl = page::self(true);
@ -88,7 +88,7 @@ class AInitAuthzPage extends AInitPage implements IBasicPage {
if ($am->isAuth()) $am->setConnected(); if ($am->isAuth()) $am->setConnected();
} }
function _ensureFormLoginAndRedirect(?string $username, ?string $password, string $destUrl): void { protected function ensureFormLoginAndRedirect(?string $username, ?string $password, string $destUrl): void {
if ($username === null && $password === null) return; if ($username === null && $password === null) return;
if (authz::manager()->formLogin($username, $password)) { if (authz::manager()->formLogin($username, $password)) {
page::redirect($destUrl); page::redirect($destUrl);

View File

@ -40,11 +40,6 @@ 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 { function getLogoutUrl(): string {
return page::bu(config::k("logout_page","_logout.php")); return page::bu(config::k("logout_page","_logout.php"));
} }

View File

@ -1,68 +0,0 @@
<?php
namespace nur\v\vp;
use nur\A;
use nur\authz;
use nur\config;
use nur\F;
use nur\v\base\AbstractPage;
use nur\v\page;
class AppExtauthPage extends AbstractPage {
/**
* @var bool faut-il afficher les variables au lieu de rediriger vers
* $ret_url?
*/
const DEBUG = false;
function isDebug(): bool {
if (!config::is_devel()) return false;
return static::DEBUG || F::get("D");
}
/** @var string nom de l'utilisateur connecté */
private $user;
function setup(): void {
$destUrl = null;
$user = false;
if ($user === false) $user = A::get($_SERVER, "REMOTE_USER", false);
if ($user === false) $user = A::get($_SERVER, "HTTP_REMOTE_USER", false);
if ($user === false) $user = A::get($_SERVER, "HTTP_X_REMOTE_USER", false);
if ($user) {
if (authz::manager()->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;
}
}
}

View File

@ -13,7 +13,7 @@ class BasicPage extends AInitAuthzPage {
function afterConfig(): void { function afterConfig(): void {
# initialiser la session avant setup. ainsi, dans les fonction beforeSetup(), # initialiser la session avant setup. ainsi, dans les fonction beforeSetup(),
# setup() et afterSetup(), la session est disponible # 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(); parent::afterConfig();
} }
} }

View File

@ -32,7 +32,7 @@ class NavigablePage extends AInitAuthzPage implements INavigablePage {
function afterConfig(): void { function afterConfig(): void {
# initialiser la session avant setup. ainsi, dans les fonction beforeSetup(), # initialiser la session avant setup. ainsi, dans les fonction beforeSetup(),
# setup() et afterSetup(), la session est disponible # 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(); parent::afterConfig();
} }

View File

@ -1,19 +0,0 @@
<?php
namespace nur\v\vp;
use nur\authz;
trait TAuthzLoginPage {
function TLoginPage_beforeSetup(): void {
authz::manager()->beforeSetup($this);
}
function beforeSetup(): void {
$this->TLoginPage_beforeSetup();
parent::beforeSetup();
}
function print(): void {
authz::manager()->print($this);
}
}

View File

@ -17,7 +17,7 @@ trait TFormLoginPage {
$username = P::get("u"); $username = P::get("u");
$password = P::get("p"); $password = P::get("p");
$destPage = F::get("d", $this->getMainUrl()); $destPage = F::get("d", $this->getMainUrl());
$this->_ensureFormLoginAndRedirect($username, $password, $destPage); $this->ensureFormLoginAndRedirect($username, $password, $destPage);
authz::manager()->checkSession($flcUsername, $flcAuthType); authz::manager()->checkSession($flcUsername, $flcAuthType);
$this->flcUsername = $flcUsername; $this->flcUsername = $flcUsername;

View File

@ -2,7 +2,6 @@
namespace nulib\php\access; namespace nulib\php\access;
use ArrayAccess; use ArrayAccess;
use nulib\A;
use nulib\cl; use nulib\cl;
/** /**
@ -119,18 +118,85 @@ class KeyAccess extends AbstractAccess {
} }
function ensureAssoc(array $keys, ?array $params=null): void { function ensureAssoc(array $keys, ?array $params=null): void {
A::ensure_assoc($this->dest, $keys, $params); $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++;
}
}
} }
function ensureKeys(array $defaults, ?array $missings, ?array $params=null): void { function ensureKeys(array $defaults, ?array $missings, ?array $params=null): void {
A::ensure_keys($this->dest, $defaults, $missings, $params); $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];
}
}
} }
function deleteMissings(array $missings, ?array $params=null): void { function deleteMissings(array $missings, ?array $params=null): void {
A::delete_missings($this->dest, $missings, $params); $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]);
}
}
} }
function ensureOrder(array $keys, ?array $params=null): void { function ensureOrder(array $keys, ?array $params=null): void {
A::ensure_order($this->dest, $keys, $params); $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);
} }
} }

View File

@ -11,10 +11,6 @@ use Traversable;
/** /**
* Class Cursor: parcours des lignes itérable * 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 array|null $value alias pour $row
* @property-read iterable|null $rows la source des lignes * @property-read iterable|null $rows la source des lignes
*/ */
@ -22,14 +18,80 @@ class Cursor implements Iterator {
const PARAMS_SCHEMA = [ const PARAMS_SCHEMA = [
"rows" => ["?iterable"], "rows" => ["?iterable"],
"rows_func" => ["?callable"], "rows_func" => ["?callable"],
"filter" => ["?array"],
"filter_func" => ["?callable"],
"map" => ["?array"],
"map_func" => ["?callable"],
"cols" => ["?array"], "cols" => ["?array"],
"cols_func" => ["?callable"], "cols_func" => ["?callable"],
"map" => ["?array"],
"map_func" => ["?callable"],
"filter" => ["?array"],
"filter_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) { function __construct(?iterable $rows=null, ?array $params=null) {
if ($rows !== null) $params["rows"] = $rows; if ($rows !== null) $params["rows"] = $rows;
@ -41,7 +103,7 @@ class Cursor implements Iterator {
$rowsGenerator = $rowsFunc; $rowsGenerator = $rowsFunc;
$rowsFunc = null; $rowsFunc = null;
} else { } else {
$rowsFunc = func::with($rowsFunc, [$rows, $this]); $rowsFunc = func::with($rowsFunc, [$rows]);
} }
} elseif ($rows instanceof Traversable) { } elseif ($rows instanceof Traversable) {
$rowsGenerator = $rows; $rowsGenerator = $rows;
@ -53,95 +115,61 @@ class Cursor implements Iterator {
$this->rowsGenerator = $rowsGenerator; $this->rowsGenerator = $rowsGenerator;
$this->rowsFunc = $rowsFunc; $this->rowsFunc = $rowsFunc;
$filter = $params["filter"] ?? null; $this->cols = $params["cols"] ?? null;
$filterFunc = $params["filter_func"] ?? null; $colsFunc = $params["cols_func"] ?? null;
if ($filterFunc !== null) $this->setFilterFunc($filterFunc); if ($colsFunc !== null) $colsFunc = func::with($colsFunc);
elseif ($filter !== null) $this->setFilter($filter); $this->colsFunc = $colsFunc;
$map = $params["map"] ?? null; $map = $params["map"] ?? null;
$mapFunc = $params["map_func"] ?? null; $mapFunc = $params["map_func"] ?? null;
if ($mapFunc !== null) $this->setMapFunc($mapFunc); if ($mapFunc !== null) {
elseif ($map !== null) $this->setMap($map); $mapFunc = func::with($mapFunc);
} elseif ($map !== null) {
$mapFunc = func::with(function(array $row) use ($map) {
return self::map_row($row, $map);
});
}
$this->mapFunc = $mapFunc;
$this->cols = $params["cols"] ?? null; $filter = $params["filter"] ?? null;
$this->setColsFunc($params["cols_func"] ?? 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;
} }
/** un générateur de lignes */ /** un générateur de lignes */
private ?Traversable $rowsGenerator; private ?Traversable $rowsGenerator;
/** une fonction de signature <code>function(mixed $rows, Cursor): ?iterable</code> */ /** une fonction de signature <code>function(Cursor): ?iterable</code> */
private ?func $rowsFunc; private ?func $rowsFunc;
/** une fonction de signature <code>function(?array $row, Cursor): bool</code> */ /** une fonction de signature <code>function(Cursor): ?array</code> */
private ?func $filterFunc = null; private ?func $colsFunc;
function setFilter(array $filter): self { /** une fonction de signature <code>function(Cursor): ?array</code> */
$this->filterFunc = func::with(function(?array $row) use ($filter) { private ?func $mapFunc;
return cl::filter($row, $filter);
});
return $this;
}
function setFilterFunc(?callable $func): self { /** une fonction de signature <code>function(Cursor): bool</code> */
if ($func === null) $this->filterFunc = null; private ?func $filterFunc;
else $this->filterFunc = func::with($func)->bind($this);
return $this;
}
/** une fonction de signature <code>function(?array $row, Cursor): ?array</code> */
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 <code>function(?array $row, Cursor): ?array</code> */
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; protected ?iterable $rows;
/** @var array|null listes des colonnes de chaque enregistrement */
public ?array $cols; public ?array $cols;
/**
* @var int index de l'enregistrement (en ne comptant pas les éléments filtrés)
*/
public int $index; public int $index;
/**
* @var int index original de l'enregistrement (en tenant compte des éléments
* filtrés)
*/
public int $origIndex; public int $origIndex;
/** @var string|int clé de l'enregistrement */
public $key; public $key;
/** @var mixed élément original récupéré depuis la source */
public $raw; public $raw;
/**
* @var array|null enregistrement après conversion en tableau et application
* du mapping
*/
public ?array $row; public ?array $row;
function __get($name) { function __get($name) {
@ -151,22 +179,18 @@ class Cursor implements Iterator {
return null; return null;
} }
protected function convertToRow($raw): ?array { protected function cols(): ?array {
return cl::withn($raw); return $this->row !== null? array_keys($this->row): null;
} }
protected function filterFunc(?array $row): bool { protected function filter(): bool {
return false; return false;
} }
protected function mapFunc(?array $row): ?array { protected function map(): ?array {
return $this->row; return $this->row;
} }
protected function colsFunc(?array $row): ?array {
return $this->row !== null? array_keys($this->row): null;
}
############################################################################# #############################################################################
# Iterator # Iterator
@ -188,30 +212,26 @@ class Cursor implements Iterator {
} }
function valid(): bool { function valid(): bool {
$colsFunc = $this->colsFunc; $cols = $this->colsFunc;
$filterFunc = $this->filterFunc; $filter = $this->filterFunc;
$mapFunc = $this->mapFunc; $map = $this->mapFunc;
while ($valid = iter::valid($this->rows)) { while ($valid = iter::valid($this->rows)) {
$this->raw = iter::current($this->rows, $this->key); $this->raw = iter::current($this->rows, $this->key);
# conversion en enregistrement
$this->key ??= $this->origIndex; $this->key ??= $this->origIndex;
$this->row = $this->convertToRow($this->raw); $this->row = cl::withn($this->raw);
# filtrage if ($filter === null) $filtered = $this->filter();
if ($filterFunc === null) $filtered = $this->filterFunc($this->row); else $filtered = $filter->invoke([$this]);
else $filtered = $filterFunc->invoke([$this->row, $this]); if (!$filtered) {
if ($filtered) { if ($map === null) $this->row = $this->map();
iter::next($this->rows); else $this->row = $map->invoke([$this]);
$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 ($this->cols === null) {
if ($colsFunc === null) $this->cols = $this->colsFunc($this->row); if ($cols === null) $this->cols = $this->cols();
else $this->cols = $colsFunc->invoke([$this->row, $this]); else $this->cols = $cols->invoke([$this]);
} }
break; break;
} else {
iter::next($this->rows);
$this->origIndex++;
} }
} }
if (!$valid) { if (!$valid) {

View File

@ -127,6 +127,7 @@ abstract class Schema implements ArrayAccess {
} }
} }
# type # type
$types = [];
$deftype = $definition["type"]; $deftype = $definition["type"];
$nullable = $definition["nullable"] ?? false; $nullable = $definition["nullable"] ?? false;
if ($deftype === null) { if ($deftype === null) {
@ -137,17 +138,7 @@ abstract class Schema implements ArrayAccess {
if (!is_string($deftype)) throw SchemaException::invalid_type($deftype); if (!is_string($deftype)) throw SchemaException::invalid_type($deftype);
$deftype = explode("|", $deftype); $deftype = explode("|", $deftype);
} }
$types = []; foreach ($deftype as $type) {
$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 = trim($type);
if ($type === null || $type === "null") { if ($type === null || $type === "null") {
$nullable = true; $nullable = true;
@ -160,18 +151,10 @@ abstract class Schema implements ArrayAccess {
} }
if ($type === "") throw SchemaException::invalid_type($type); if ($type === "") throw SchemaException::invalid_type($type);
$type = cl::get(ref_types::ALIASES, $type, $type); $type = cl::get(ref_types::ALIASES, $type, $type);
if ($args === null) { $types = array_merge($types, explode("|", $type));
$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["type"] = $types;
$definition["nullable"] = $nullable; $definition["nullable"] = $nullable;
@ -241,7 +224,7 @@ abstract class Schema implements ArrayAccess {
$types = $definition["type"]; $types = $definition["type"];
$nullable = $definition["nullable"]; $nullable = $definition["nullable"];
# s'il n'y a qu'une seul type, l'instancier tout de suite # s'il n'y a qu'une seul type, l'instancier tout de suite
if (is_array($types) && count($types) == 1 && cl::first($types) !== null) { if (is_array($types) && count($types) == 1 && $types[0] !== null) {
foreach ($types as $key => $name) { foreach ($types as $key => $name) {
if ($key === 0) { if ($key === 0) {
$args = null; $args = null;

View File

@ -5,45 +5,19 @@ use nulib\cl;
use nulib\schema\_scalar\ScalarSchema; use nulib\schema\_scalar\ScalarSchema;
use nulib\schema\Result; use nulib\schema\Result;
use nulib\schema\Schema; use nulib\schema\Schema;
use nulib\str;
class tarray extends _tstring { class tarray extends _tstring {
const NAME = "array"; const NAME = "array";
const TRIM = true; const SPLIT_PATTERN = '/\s+/';
const NORM_NL = true; const FORMAT = " ";
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 { public static function get_params_from_definition(?array $definition): ?array {
$params = parent::get_params_from_definition($definition); $params = parent::get_params_from_definition($definition);
$sep = $definition["sep"] ?? null; $splitPattern = $definition["split_pattern"] ?? null;
if ($sep !== null) { if ($splitPattern !== null) $params["split_pattern"] = $splitPattern;
if (!is_array($sep)) { $format = $definition["format"] ?? null;
$sep = cl::get(self::SEP_MAP, $sep, $sep); if ($format !== null) $params["format"] = $format;
$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; return $params;
} }
@ -69,9 +43,8 @@ class tarray extends _tstring {
} }
function parse(string $value) { function parse(string $value) {
$sep = $this->params["parse_sep"] ?? static::DEFAULT_PARSE_SEP; $pattern = $this->params["split_pattern"] ?? static::SPLIT_PATTERN;
if ($sep !== false) $value = str::split($sep, $value); return preg_split($pattern, $value);
return $value;
} }
/** /**
@ -90,7 +63,7 @@ class tarray extends _tstring {
function format($value, $format=null): string { function format($value, $format=null): string {
if ($value === null) return ""; if ($value === null) return "";
$format ??= $this->params["format"] ?? static::DEFAULT_FORMAT_SEP; $format ??= $this->params["format"] ?? static::FORMAT;
return implode($format, $value); return implode($format, $value);
} }
} }

View File

@ -16,7 +16,7 @@ class CursorTest extends TestCase {
function test_map_row() { function test_map_row() {
$cursor = new class extends Cursor { $cursor = new class extends Cursor {
function mapRow(array $row, ?array $map): array { function mapRow(array $row, ?array $map): array {
return cl::map($row, $map); return self::map_row($row, $map);
} }
}; };
$row = ["a" => 1, "b" => 2, "c" => 3, "x" => 99]; $row = ["a" => 1, "b" => 2, "c" => 3, "x" => 99];
@ -32,7 +32,7 @@ class CursorTest extends TestCase {
function test_filter_row() { function test_filter_row() {
$cursor = new class extends Cursor { $cursor = new class extends Cursor {
function filterRow(array $row, $filter): bool { function filterRow(array $row, $filter): bool {
return cl::filter($row, $filter); return self::filter_row($row, $filter);
} }
}; };
$row = ["a" => 1, "b" => 2, "c" => 3, "x" => 99]; $row = ["a" => 1, "b" => 2, "c" => 3, "x" => 99];

View File

@ -1,56 +0,0 @@
<?php
namespace nulib\schema\types;
use nulib\schema\Schema;
use nur\t\TestCase;
class arrayTest extends TestCase {
function testSchema() {
$value = " first second ";
Schema::nw($value, null, "array");
self::assertSame(["first", "second"], $value);
$value = " first second ";
Schema::nw($value, null, [
"array", "sep" => "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);
}
}