mirror of
https://github.com/aljazceru/CTFd.git
synced 2026-02-04 13:54:26 +01:00
Add support for only viewing hints after unlocking another hint (#2074)
* Add support for only viewing hints after unlocking another hint * Closes #2007
This commit is contained in:
@@ -120,6 +120,33 @@ class Hint(Resource):
|
||||
user = get_current_user()
|
||||
hint = Hints.query.filter_by(id=hint_id).first_or_404()
|
||||
|
||||
if hint.requirements:
|
||||
requirements = hint.requirements.get("prerequisites", [])
|
||||
|
||||
# Get the IDs of all hints that the user has unlocked
|
||||
all_unlocks = HintUnlocks.query.filter_by(account_id=user.account_id).all()
|
||||
unlock_ids = {unlock.id for unlock in all_unlocks}
|
||||
|
||||
# Filter out hint IDs that don't exist
|
||||
all_hint_ids = {h.id for h in Hints.query.with_entities(Hints.id).all()}
|
||||
prereqs = set(requirements).intersection(all_hint_ids)
|
||||
|
||||
# If the user has the necessary unlocks or is admin we should allow them to view
|
||||
if unlock_ids >= prereqs or is_admin():
|
||||
pass
|
||||
else:
|
||||
return (
|
||||
{
|
||||
"success": False,
|
||||
"errors": {
|
||||
"requirements": [
|
||||
"You must unlock other hints before accessing this hint"
|
||||
]
|
||||
},
|
||||
},
|
||||
403,
|
||||
)
|
||||
|
||||
view = "unlocked"
|
||||
if hint.cost:
|
||||
view = "locked"
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="text-muted">
|
||||
<label>
|
||||
Hint<br />
|
||||
<small>Markdown & HTML are supported</small>
|
||||
</label>
|
||||
@@ -50,6 +50,31 @@
|
||||
v-model.lazy="cost"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
Requirements<br />
|
||||
<small
|
||||
>Hints that must be unlocked before unlocking this
|
||||
hint</small
|
||||
>
|
||||
</label>
|
||||
<div
|
||||
class="form-check"
|
||||
v-for="hint in hints"
|
||||
:key="hint.id"
|
||||
>
|
||||
<label class="form-check-label cursor-pointer">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
:value="hint.id"
|
||||
v-model="selectedHints"
|
||||
/>
|
||||
{{ hint.cost }} - {{ hint.id }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="hint-id-for-hint" name="id" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,11 +99,13 @@
|
||||
export default {
|
||||
name: "HintCreationForm",
|
||||
props: {
|
||||
challenge_id: Number
|
||||
challenge_id: Number,
|
||||
hints: Array
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
cost: 0
|
||||
cost: 0,
|
||||
selectedHints: []
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -89,11 +116,11 @@ export default {
|
||||
return this.$refs.content.value;
|
||||
},
|
||||
submitHint: function() {
|
||||
console.log(this.co);
|
||||
let params = {
|
||||
challenge_id: this.$props.challenge_id,
|
||||
content: this.getContent(),
|
||||
cost: this.getCost()
|
||||
cost: this.getCost(),
|
||||
requirements: { prerequisites: this.selectedHints }
|
||||
};
|
||||
CTFd.fetch("/api/v1/hints", {
|
||||
method: "POST",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="form-group">
|
||||
<label class="text-muted">
|
||||
<label>
|
||||
Hint<br />
|
||||
<small>Markdown & HTML are supported</small>
|
||||
</label>
|
||||
@@ -52,6 +52,31 @@
|
||||
v-model.lazy="cost"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
Requirements<br />
|
||||
<small
|
||||
>Hints that must be unlocked before unlocking this
|
||||
hint</small
|
||||
>
|
||||
</label>
|
||||
<div
|
||||
class="form-check"
|
||||
v-for="hint in otherHints"
|
||||
:key="hint.id"
|
||||
>
|
||||
<label class="form-check-label cursor-pointer">
|
||||
<input
|
||||
class="form-check-input"
|
||||
type="checkbox"
|
||||
:value="hint.id"
|
||||
v-model="selectedHints"
|
||||
/>
|
||||
{{ hint.content }} - {{ hint.cost }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -78,14 +103,25 @@ import { bindMarkdownEditor } from "../../styles";
|
||||
export default {
|
||||
name: "HintEditForm",
|
||||
props: {
|
||||
hint_id: Number
|
||||
challenge_id: Number,
|
||||
hint_id: Number,
|
||||
hints: Array
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
cost: 0,
|
||||
content: null
|
||||
content: null,
|
||||
selectedHints: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// Get all hints besides the current one
|
||||
otherHints: function() {
|
||||
return this.hints.filter(hint => {
|
||||
return hint.id !== this.$props.hint_id;
|
||||
});
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
hint_id: {
|
||||
immediate: true,
|
||||
@@ -114,6 +150,7 @@ export default {
|
||||
let hint = response.data;
|
||||
this.cost = hint.cost;
|
||||
this.content = hint.content;
|
||||
this.selectedHints = hint.requirements?.prerequisites || [];
|
||||
// Wait for Vue to update the DOM
|
||||
this.$nextTick(() => {
|
||||
// Wait a little longer because we need the modal to appear.
|
||||
@@ -138,7 +175,8 @@ export default {
|
||||
let params = {
|
||||
challenge_id: this.$props.challenge_id,
|
||||
content: this.getContent(),
|
||||
cost: this.getCost()
|
||||
cost: this.getCost(),
|
||||
requirements: { prerequisites: this.selectedHints }
|
||||
};
|
||||
|
||||
CTFd.fetch(`/api/v1/hints/${this.$props.hint_id}`, {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<HintCreationForm
|
||||
ref="HintCreationForm"
|
||||
:challenge_id="challenge_id"
|
||||
:hints="hints"
|
||||
@refreshHints="refreshHints"
|
||||
/>
|
||||
</div>
|
||||
@@ -11,7 +12,9 @@
|
||||
<div>
|
||||
<HintEditForm
|
||||
ref="HintEditForm"
|
||||
:challenge_id="challenge_id"
|
||||
:hint_id="editing_hint_id"
|
||||
:hints="hints"
|
||||
@refreshHints="refreshHints"
|
||||
/>
|
||||
</div>
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -420,6 +420,11 @@ const displayUnlock = id => {
|
||||
|
||||
const loadHint = id => {
|
||||
CTFd.api.get_hint({ hintId: id }).then(response => {
|
||||
if (!response.success) {
|
||||
let msg = Object.values(response.errors).join("\n");
|
||||
alert(msg);
|
||||
return;
|
||||
}
|
||||
if (response.data.content) {
|
||||
displayHint(response.data);
|
||||
return;
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user