预览
嵌入代码
将代码粘贴到页面中希望显示测验的位置。
须知
- 如果你的网站使用内容安全策略(CSP),请在 script-src 中添加:https://www.qwissit.com
- 该小组件不设置 Cookie,也不在浏览器中存储任何内容。它只从 Qwissit 加载脚本和题目。
- 题目会自动更换。如需修改颜色或文字,请在此生成新代码并替换旧代码。
可选:通过你自己的域名嵌入
如果你不希望向第三方服务器发出任何请求,可以由你的 Web 服务器提供测验:它把 /qwissit/ 这样的路径转发给 Qwissit。这样访客的浏览器只与你的域名通信,Qwissit 只能看到你服务器的地址。上面的直接嵌入仍是简单的默认方式。
1. 配置 Web 服务器:把 /qwissit/ 转发到 https://www.qwissit.com/widget/v1/,不传递访客的 Cookie、IP 地址或其他信息。
nginx
location ^~ /qwissit/ {
proxy_pass https://www.qwissit.com/widget/v1/;
proxy_set_header Host www.qwissit.com;
proxy_ssl_server_name on;
proxy_set_header X-Forwarded-For "";
proxy_set_header X-Real-IP "";
proxy_set_header Cookie "";
proxy_set_header Referer "";
proxy_set_header User-Agent "qwissit-proxy";
proxy_hide_header Set-Cookie;
}Apache (mod_proxy, mod_ssl, mod_headers)
SSLProxyEngine On
<Location "/qwissit/">
ProxyPass "https://www.qwissit.com/widget/v1/"
ProxyAddHeaders Off
RequestHeader unset Cookie
RequestHeader unset Referer
RequestHeader set User-Agent "qwissit-proxy"
Header unset Set-Cookie
</Location>Caddy
handle_path /qwissit/* {
rewrite * /widget/v1{path}?{query}
reverse_proxy https://www.qwissit.com {
header_up Host {upstream_hostport}
header_up -X-Forwarded-For
header_up -Cookie
header_up -Referer
header_up User-Agent qwissit-proxy
header_down -Set-Cookie
}
}PHP (Shared Hosting)
无法修改服务器配置(共享主机)?下载 PHP 文件,以 qwissit-proxy.php 保存到网站目录,并把 /qwissit-proxy.php/qwissit-widget.js 用作脚本地址。它只转发小组件的文件。
2. 替换上面代码中的脚本地址:
<script src="/qwissit/qwissit-widget.js" async></script>
3. 自检:用你的域名发起此请求,必须返回 200 和 content-type: text/javascript。返回 404 时请查看“无法正常工作时”中的各项;返回 HTML 页面说明请求被你的 CMS 接管了。
curl -I https://example.org/qwissit/qwissit-widget.js
然后在浏览器中用开发者工具(F12)打开“网络”标签:不应出现任何发往 www.qwissit.com 的请求。
无法正常工作时
- nginx 返回 404:像 location ~* \.(js|css)$ 这样的静态文件规则优先于普通的前缀规则,于是 nginx 会在你的服务器上查找 qwissit-widget.js。因此代码片段写的是 location ^~ /qwissit/——^~ 确保这个路径始终被转发。
- 修改需重新加载后才生效:nginx -t && nginx -s reload;Apache 用 apachectl configtest && systemctl reload apache2。在 Docker 中如果单独挂载了配置文件,替换该文件(rsync、cp)后容器仍会看到旧版本——这时只能重启容器(docker compose restart <服务>)。
- Apache:必须启用 proxy、proxy_http、ssl 和 headers 模块(a2enmod proxy proxy_http ssl headers)。这些规则应写在服务器或 VirtualHost 配置中,而不是 .htaccess 中。如果你只能使用 .htaccess,请改用 PHP 文件。
- PHP 文件:它必须确实位于网站目录中(TYPO3 中为 public/),否则请求会被 CMS 接管。服务器必须把文件后面的路径(PATH_INFO)传给 PHP:Apache 中设置 AcceptPathInfo On(很多主机默认如此),nginx 中在 PHP 规则里这样写:
location ~ \.php(/|$) { fastcgi_split_path_info ^(.+\.php)(/.*)$; fastcgi_param PATH_INFO $fastcgi_path_info; # … your usual fastcgi_pass / include fastcgi_params }
可选:防篡改地验证结果
你想在自己的网站上记录某人通过了测验——例如勾选一节课或发放积分?那就让 Qwissit 确认这个结果。这样谁也无法伪造。
流程如下
- 你的页面给测验一个一次性代码,并记住它。
- 访客完成测验。Qwissit 亲自检查每个答案;答错的题目会再次出现,直到全部答对。
- 结束时,你的页面会收到一份只有 Qwissit 才能签发的凭证,并把它发送到你的服务器。
- 你的服务器向 Qwissit 询问该凭证是否真实、是否与你的代码和测验相符。只有这样才会保存结果。
你需要添加什么
两处,都用 PHP。你的访客需要登录(会话),因为结果要归属于某个人。
A) 在测验所在的页面上: 这段代码替代普通代码片段。把 data-set 和 data-course 改成你的测验——数值见上面的代码。
<?php
session_start(); // your visitors are signed in
$nonce = $_SESSION['quiz_nonce']['http'] ??= rtrim(strtr(base64_encode(random_bytes(16)), '+/', '-_'), '=');
?>
<div id="quiz" data-qwissit-widget data-set="12" data-course="13" data-lang="de"
data-verify="<?= $nonce ?>"></div>
<script src="https://www.qwissit.com/widget/v1/qwissit-widget.js" async></script>
<script>
document.getElementById('quiz').addEventListener('qwissit:finish', function (e) {
if (!e.detail.passed || !e.detail.token) return;
fetch('quiz-complete.php', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'lesson=http&token=' + encodeURIComponent(e.detail.token)
}).then(function (r) {
if (r.ok) { /* done: e.g. show a tick */ }
});
});
</script>
B) 新文件 quiz-complete.php 放在同一目录中。在 QUIZZES 中填入相同的 set 和 course 数值,并在标记处按你网站的方式保存结果。
<?php
// quiz-complete.php – checks the result with Qwissit and saves it only if everything matches
session_start();
const QUIZZES = ['http' => ['set' => 12, 'course' => 13]]; // same values as data-set / data-course
$lesson = (string)($_POST['lesson'] ?? '');
$token = (string)($_POST['token'] ?? '');
$quiz = QUIZZES[$lesson] ?? null;
$nonce = (string)($_SESSION['quiz_nonce'][$lesson] ?? '');
if (!$quiz || $nonce === '' || $token === '') { http_response_code(400); exit; }
$ch = curl_init('https://www.qwissit.com/widget/v1/verify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(['token' => $token]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 4,
]);
$r = json_decode((string)curl_exec($ch), true);
curl_close($ch);
if (!is_array($r)) { http_response_code(503); exit; } // Qwissit not reachable: save nothing
if (($r['valid'] ?? false) !== true
|| !hash_equals($nonce, (string)($r['nonce'] ?? ''))
|| (int)$r['set'] !== $quiz['set'] || (int)$r['course'] !== $quiz['course']) {
http_response_code(403); exit; // forged, already used, expired or another quiz
}
unset($_SESSION['quiz_nonce'][$lesson]); // the code counts only once
// ▶ save the result here, e.g. user + $lesson + date (+ $r['wrong'] = failed attempts)
header('Content-Type: application/json');
echo json_encode(['ok' => true]);
只有当 Qwissit 确认凭证、凭证属于你的一次性代码并来自正确的测验时,这个文件才会保存。之后代码即失效,因此每个结果只计一次。如果暂时无法连接 Qwissit,则不保存任何内容——请让访客稍后再试。凭证有效期为一小时。
需要注意
- 为什么要绕道你的服务器?小组件也会直接在浏览器中报告测验结束(事件 qwissit:finish),但任何懂点技术的人都能自己触发它。用于显示足够了,但不能用于积分、证书或解锁。
- 只向已登录的访客显示带代码的测验,不要为其他人创建会话——否则每个机器人都会创建一个。
- 像保护其他表单一样保护 quiz-complete.php:检查登录、只接受来自你自己网站的请求、限制请求次数。
- 成功之后,访客下次访问时会得到新代码,可以重做测验。
- 测试方法:编造的凭证、第二次提交同一凭证以及另一个测验的凭证,都必须被拒绝。
用于隐私政策的文本
一份可按你的网站调整的建议,不构成法律意见。
直接嵌入
本网站嵌入了 Qwissit 的测验(www.qwissit.com)。加载时,你的浏览器会从 www.qwissit.com 获取脚本和题目,此时会传输你的 IP 地址。测验不设置 Cookie,不在你的浏览器中存储任何内容,也不保存你的答案。法律依据是我们提供互动学习内容的合法利益(GDPR 第 6 条第 1 款 f 项)。详情:https://www.qwissit.com/en/privacy#widget
通过你自己的域名(代理)
本网站嵌入了 Qwissit 的测验。脚本和题目由我们自己的服务器提供,它从 Qwissit 获取这些内容。你的浏览器不会连接 Qwissit,Qwissit 既不会收到你的 IP 地址,也不会收到 Cookie 或其他关于你的信息。测验不设置 Cookie,不在你的浏览器中存储任何内容,也不保存你的答案。详情:https://www.qwissit.com/en/privacy#widget