diff --git a/db/__init__.py b/db/__init__.py index 25f3a850..884dd607 100644 --- a/db/__init__.py +++ b/db/__init__.py @@ -34,6 +34,12 @@ from .games.create_game import create_game from .games.update_game_name import update_game_name from .games.delete_game import delete_game from .games.move_game import move_game +from .games.get_xapi_settings import get_xapi_settings +from .games.set_xapi_settings import set_xapi_settings +from .games.get_success_threshold import get_success_threshold +from .games.set_success_threshold import set_success_threshold +from .games.get_scorm_version import get_scorm_version +from .games.set_scorm_version import set_scorm_version from .games.game_type_catalog import ( ONBOARDING_TYPES, DEFAULT_ONBOARDING_TYPE, get_onboarding_type, get_onboarding_type_raw, set_onboarding_type, diff --git a/db/games/get_scorm_version.py b/db/games/get_scorm_version.py new file mode 100644 index 00000000..fbe0b086 --- /dev/null +++ b/db/games/get_scorm_version.py @@ -0,0 +1,19 @@ +from ..connection import connect + +DEFAULT_SCORM_VERSION = "1.2" +VALID_SCORM_VERSIONS = ("1.2", "2004") + + +def get_scorm_version(slug): + """Version SCORM exportée pour ce jeu (voir publish/build_scorm_package.py, + publish/scorm_manifest.py) — '1.2' par défaut (compatibilité la plus + large) ou '2004' (3rd/4th edition, sépare completion_status/ + success_status : voir static/js/play/offline/scorm2004-api.js, corrige + la limite SCORM 1.2 où un statut unique doit coder à la fois + complétion ET réussite).""" + conn = connect(slug) + row = conn.execute("SELECT value FROM _meta WHERE key = 'scorm_version'").fetchone() + conn.close() + if row is None or row["value"] not in VALID_SCORM_VERSIONS: + return DEFAULT_SCORM_VERSION + return row["value"] diff --git a/db/games/get_success_threshold.py b/db/games/get_success_threshold.py new file mode 100644 index 00000000..8f5b5cbb --- /dev/null +++ b/db/games/get_success_threshold.py @@ -0,0 +1,19 @@ +from ..connection import connect + +DEFAULT_SUCCESS_THRESHOLD_PERCENT = 70 + + +def get_success_threshold(slug): + """Seuil de réussite (% de bonnes réponses au quiz) de CE jeu — stocké + dans _meta (clé 'success_threshold_percent'), même convention que + get_xapi_settings.py. Détermine le statut SCORM/xAPI de fin de partie + (reussi/echoue, voir static/js/play/dialogue-box-controller.js:: + forgeSyncAllQuestsCompletionToScorm) : 70% par défaut si jamais réglé.""" + conn = connect(slug) + row = conn.execute( + "SELECT value FROM _meta WHERE key = 'success_threshold_percent'" + ).fetchone() + conn.close() + if row is None or row["value"] in (None, ""): + return DEFAULT_SUCCESS_THRESHOLD_PERCENT + return int(row["value"]) diff --git a/db/games/get_xapi_settings.py b/db/games/get_xapi_settings.py new file mode 100644 index 00000000..7e7cb1bf --- /dev/null +++ b/db/games/get_xapi_settings.py @@ -0,0 +1,23 @@ +from ..connection import connect + + +def get_xapi_settings(slug): + """Réglages xAPI de CE jeu (voir set_xapi_settings.py — stockés dans + _meta, même convention que 'name'/'onboarding_type', voir + db/games/game_type_catalog.py) : URL du LRS (Learning Record Store) + + identifiants, saisis une seule fois par le créateur du jeu (voir + routes/publish/xapi_settings.py) plutôt que négociés à chaque + lancement (jamais le protocole cmi5 complet, voir publish/ + build_scorm_package.py::build_scorm_zip). Chaînes vides si non + configuré — `endpoint` vide signifie "xAPI désactivé pour ce jeu".""" + conn = connect(slug) + rows = conn.execute( + "SELECT key, value FROM _meta WHERE key IN ('xapi_lrs_endpoint', 'xapi_lrs_login', 'xapi_lrs_password')" + ).fetchall() + conn.close() + values = {row["key"]: row["value"] for row in rows} + return { + "endpoint": values.get("xapi_lrs_endpoint") or "", + "login": values.get("xapi_lrs_login") or "", + "password": values.get("xapi_lrs_password") or "", + } diff --git a/db/games/set_scorm_version.py b/db/games/set_scorm_version.py new file mode 100644 index 00000000..21279a6d --- /dev/null +++ b/db/games/set_scorm_version.py @@ -0,0 +1,15 @@ +from ..connection import connect +from .get_scorm_version import VALID_SCORM_VERSIONS + + +def set_scorm_version(slug, version): + """Enregistre la version SCORM exportée — voir get_scorm_version.py.""" + if version not in VALID_SCORM_VERSIONS: + raise ValueError("version SCORM invalide : {!r}".format(version)) + conn = connect(slug) + conn.execute( + "INSERT OR REPLACE INTO _meta (key, value) VALUES ('scorm_version', ?)", + (version,), + ) + conn.commit() + conn.close() diff --git a/db/games/set_success_threshold.py b/db/games/set_success_threshold.py new file mode 100644 index 00000000..cb327628 --- /dev/null +++ b/db/games/set_success_threshold.py @@ -0,0 +1,14 @@ +from ..connection import connect + + +def set_success_threshold(slug, percent): + """Enregistre le seuil de réussite (0-100) — voir get_success_threshold.py.""" + percent = max(0, min(100, int(percent))) + conn = connect(slug) + conn.execute( + "INSERT OR REPLACE INTO _meta (key, value) VALUES ('success_threshold_percent', ?)", + (str(percent),), + ) + conn.commit() + conn.close() + return percent diff --git a/db/games/set_xapi_settings.py b/db/games/set_xapi_settings.py new file mode 100644 index 00000000..0c63315b --- /dev/null +++ b/db/games/set_xapi_settings.py @@ -0,0 +1,24 @@ +from ..connection import connect + + +def set_xapi_settings(slug, endpoint, login, password=None): + """Enregistre les réglages xAPI de CE jeu (voir get_xapi_settings.py). + `password=None` (champ laissé vide côté formulaire, voir + routes/publish/xapi_settings.py) laisse le mot de passe déjà + enregistré INCHANGÉ — jamais écrasé par une chaîne vide, pour ne pas + obliger à le retaper à chaque modification de l'URL/du login, et pour + que la route GET puisse ne jamais renvoyer sa valeur au navigateur + (juste un booléen "déjà configuré").""" + conn = connect(slug) + conn.execute( + "INSERT OR REPLACE INTO _meta (key, value) VALUES ('xapi_lrs_endpoint', ?)", (endpoint or "",) + ) + conn.execute( + "INSERT OR REPLACE INTO _meta (key, value) VALUES ('xapi_lrs_login', ?)", (login or "",) + ) + if password is not None: + conn.execute( + "INSERT OR REPLACE INTO _meta (key, value) VALUES ('xapi_lrs_password', ?)", (password,) + ) + conn.commit() + conn.close() diff --git a/publish/accessibility_declaration.py b/publish/accessibility_declaration.py new file mode 100644 index 00000000..7ddd975e --- /dev/null +++ b/publish/accessibility_declaration.py @@ -0,0 +1,67 @@ +"""Déclaration d'accessibilité exportable (obligation légale RGAA pour +tout service numérique concerné, voir roadmap phase 1.2) — PAS un audit +certifié : un résumé honnête, généré à partir de ce que le moteur de jeu +implémente RÉELLEMENT aujourd'hui (voir static/js/play/triggers.js, +dialogue-box-controller.js, screens/scenes/render_scene_object.py), à +compléter/faire valider par le créateur du jeu avant publication.""" +from datetime import date + +_DECLARATION_TEMPLATE = """Déclaration d'accessibilité — {game_name} +Générée le {today} par Forge Engine + +Cette déclaration résume l'état d'accessibilité du jeu de formation +"{game_name}" au regard du RGAA (Référentiel Général d'Amélioration de +l'Accessibilité, version française de WCAG 2.1 niveau AA). Elle décrit ce +que le moteur Forge Engine implémente pour TOUT jeu exporté — elle ne +remplace pas un audit RGAA complet réalisé par un expert accessibilité, et +ne couvre pas le contenu propre à ce jeu (texte, images, choix de +couleurs) qui reste sous la responsabilité de son créateur. + +CRITÈRES PRIS EN CHARGE PAR LE MOTEUR +-------------------------------------- +- Navigation clavier : tout objet de scène réagissant "Au clic" ou "Au + survol" est focusable (Tab) et activable au clavier (Entrée/Espace), + en plus de la souris. Les boutons de dialogue/quiz sont de vrais +