Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

YDA-5720 config publication terms #341

Merged
merged 25 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a9059eb
add basic ui for publication terms
FuHsinyu Aug 1, 2024
914eda2
add endpoint to retrive local terms and add preview window for terms …
FuHsinyu Aug 1, 2024
e62bb72
add reading default terms from irods, if current terms not exist in /…
FuHsinyu Aug 1, 2024
38f7510
add endpoint to update terms displayed when user click publication
FuHsinyu Aug 1, 2024
0a9f535
add saving terms to /config if there is no such in the /config directory
FuHsinyu Aug 1, 2024
ef1f03c
add the modal for preview publication terms
FuHsinyu Aug 2, 2024
d47d2c2
let preiview function retrive current terms from endpoints instead of…
FuHsinyu Aug 2, 2024
a8cdfeb
Rearrange UI components to make admin page slightly consistent in vis…
FuHsinyu Aug 2, 2024
7cda848
Move create_preview js script out of the html to a separate .js file …
FuHsinyu Aug 5, 2024
034198d
refacorized codes
FuHsinyu Aug 5, 2024
94c7668
Add type hint and doc strings
FuHsinyu Aug 5, 2024
1aaa405
Remove unused codes
FuHsinyu Aug 5, 2024
a9f5492
Clean codes
FuHsinyu Aug 5, 2024
247ea0f
Formatted codes
FuHsinyu Aug 5, 2024
ff15b0b
Fix the vault js to fetch terms from flask endpoint
FuHsinyu Aug 5, 2024
f106c69
Let the preview function preview the terms in textarea
FuHsinyu Aug 6, 2024
d6afcd7
Add DOMpurify to santinize html content
FuHsinyu Aug 6, 2024
ddb9671
move purify to /static/lib/ and add purify map
FuHsinyu Aug 6, 2024
a778e35
Merge branch 'development' into YDA-5720-config-publication-terms
lwesterhof Aug 6, 2024
6af3638
Fix javascript problems
lwesterhof Aug 6, 2024
054fb80
add comment to global createPreview for JS lint
FuHsinyu Aug 6, 2024
6c15755
disable no-unused-vars for createPreview
FuHsinyu Aug 6, 2024
7c954c2
Refactor to remove eslint ignore
lwesterhof Aug 6, 2024
7a47233
Add missing document ready
lwesterhof Aug 6, 2024
1d20bcf
Fix id name
lwesterhof Aug 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions admin/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
__copyright__ = 'Copyright (c) 2024, Utrecht University'
__license__ = 'GPLv3, see LICENSE'

import html
import json
from functools import wraps
from os import path
from typing import Any, Callable, Dict, Optional

from flask import (
abort, Blueprint, current_app as app, flash, Flask, g, redirect,
abort, Blueprint, current_app as app, flash, Flask, g, jsonify, redirect,
render_template, request, Response, session, url_for
)
from jinja2 import ChoiceLoader, FileSystemLoader
Expand All @@ -36,7 +37,9 @@ def index() -> Response:
if session['admin']:
# reload theme options
theme_directories = get_theme_directories(app.config.get('YODA_THEME_PATH'))
return render_template("admin.html", theme_directories=theme_directories)
publication_terms_text = get_publication_terms()
return render_template("admin.html", theme_directories=theme_directories, publication_terms=publication_terms_text)

else:
return abort(403)

Expand Down Expand Up @@ -189,3 +192,72 @@ def set_theme_loader(app: Flask, remove_cache: Optional[bool] = False) -> None:
if remove_cache:
if hasattr(app.jinja_env, 'cache'):
app.jinja_env.cache = {}


@admin_bp.route('/set_publication_terms', methods=['POST'])
@admin_required
def set_publication_terms() -> Response:
"""Receives new publication terms from a POST request, sanitizes, and saves them.

:return: A redirection response to the admin index page.
"""
# Retrives new terms from the POST request
new_terms = request.form['publicationTerms']
sanitized_terms = html.escape(new_terms)

# Save new terms to local file
try:
publication_terms_path = path.join(app.config['YODA_CONFIG_PATH'], 'publication_terms.html')
with open(publication_terms_path, 'w') as file:
file.write(sanitized_terms)
flash("Publication terms updated successfully.", "success")
return redirect(url_for("admin_bp.index"))
except Exception:
flash("Failed to update publication terms", "error")

return redirect(url_for("admin_bp.index"))


@admin_bp.route("/get_publication_terms")
def publication_terms() -> Response:
"""Retrieve and return the current publication terms as JSON.

:return: JSON object containing the current publication terms.
"""
terms = get_publication_terms()
return jsonify({'terms': terms})


def get_publication_terms() -> Optional[str]:
"""Retrieve publication terms from a local file or from an iRODS API fallback.

:return: A string containing the html-like publication terms.
"""
publication_terms_path = path.join(app.config['YODA_CONFIG_PATH'], 'publication_terms.html')

# Attempt to read from local file
if path.exists(publication_terms_path):
try:
with open(publication_terms_path, 'r') as file:
publication_terms_html = file.read()
return html.unescape(publication_terms_html) # Convert escaped terms to html
except Exception:
flash("Failed to load publication terms from file.", "error")

# Fallback to API if the file does not exist or an error occurred
try:
response = api.call('vault_get_publication_terms', {})
publication_terms_html = response["data"]

# Save the data to a local file if it was fetched from the API
try:
with open(publication_terms_path, 'w') as file:
file.write(html.escape(publication_terms_html))
except Exception:
flash("Failed to save publication terms to file", "error")

return publication_terms_html
except Exception:
flash("Failed to load publication terms from API", "error")

return "Error: failed to read publication terms"
14 changes: 14 additions & 0 deletions admin/static/admin/js/create_preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Function to preview publication terms in a specific modal
function createPreview() {
// Get the content of the textarea and sanitize it
var termsText = document.getElementById('publicationTerms').value;
const sanitizedContent = DOMPurify.sanitize(termsText);

// Set the content in the modal body for preview
const modalBody = document.querySelector('#confirmAgreementConditions .modal-body');
modalBody.innerHTML = sanitizedContent;

// Show the modal
var myModal = new bootstrap.Modal(document.getElementById('confirmAgreementConditions'));
myModal.show();
}
65 changes: 63 additions & 2 deletions admin/templates/admin/admin.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

{% block title %}{{ super() }} ‐ Administration{% endblock title %}

{% block scripts %}
<script src="{{ url_for('admin_bp.static', filename='js/create_preview.js') }}"></script>
lwesterhof marked this conversation as resolved.
Show resolved Hide resolved
<script src="{{ url_for('static', filename='js/purify.min.js') }}"></script>
lwesterhof marked this conversation as resolved.
Show resolved Hide resolved
{% endblock scripts %}

{% block content %}
<div class="container mt-4">
<header>
Expand All @@ -26,7 +31,8 @@ <h2 class="card-title">Set Maintenance Banner</h2>
</div>
<!-- Checkbox to mark the banner as important -->
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="importance" name="importance" {{ 'checked' if config.get('banner', {}).get('importance', False) else '' }}>
<input type="checkbox" class="form-check-input" id="importance" name="importance"
{{ 'checked' if config.get('banner', {}).get('importance', False) else '' }}>
<label class="form-check-label" for="importance">Mark as Important</label>
</div>
<button type="submit" class="btn btn-primary" name="Set Banner">Set Banner</button>
Expand All @@ -39,6 +45,7 @@ <h2 class="card-title">Set Maintenance Banner</h2>
</div>
</div>
</section>

<!-- Theme Change section-->
<section class="card my-3">
<div class="card-body">
Expand All @@ -51,14 +58,68 @@ <h2 class="card-title">Change Portal Theme</h2>
<!-- Add default theme option -->
{% set current_theme = config.get('YODA_THEME', 'uu') %}
{% for folder in theme_directories %}
<option value="{{ folder }}" {% if folder == current_theme %}selected{% endif %}> {{ config.get('YODA_THEME_MAPPING').get(folder, folder) }}</option>
<option value="{{ folder }}" {% if folder == current_theme %}selected{% endif %}> {{ config.get('YODA_THEME_MAPPING').get(folder, folder) }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-primary" name="Change Theme">Change Theme</button>
</form>
</div>
</section>

<!-- Update Publication Terms -->
<section class="card my-3">
<div class="card-body">
<h2 class="card-title">Update Publication Terms</h2>
<div class="d-flex justify-content-start align-items-end" style="width: 100%;">
<!-- Form to update publication terms -->
<form action="{{ url_for('admin_bp.set_publication_terms') }}" method="POST"
class="flex-fill me-2 needs-validation" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label for="publicationTerms">Current Publication Terms:</label>
<textarea class="form-control" id="publicationTerms" name="publicationTerms" rows="10"
style="width: 110%;" required>{{ publication_terms|safe }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Update Terms</button>
</form>
<!-- Form to preview updated terms -->
<form action="javascript:void(0);" method="POST" class="flex-shrink-1">
lwesterhof marked this conversation as resolved.
Show resolved Hide resolved
<button type="button" class="btn btn-secondary" onclick="createPreview()">Preview Terms</button>
</form>
</div>
</div>
</section>

<!-- Confirmation Agreement Conditions Modal, copied from vault/browse.html -->
<div class="modal" tabindex="-1" role="dialog" id="confirmAgreementConditions">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<!-- Updated content will be displayed here -->
</div>
<div class="modal-footer">
<div class="row">
<div class="col-sm-12">
<fieldset>
<input type="checkbox" class="confirm-conditions" id="checkbox-confirm-conditions">
<label for="checkbox-confirm-conditions">Please confirm that you agree with the
above</label>
</fieldset>
</div>
</div>
<div class="row">
<div class="col-sm-12">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button"
class="btn btn-primary ms-2 action-confirm-submit-for-publication">Confirm
agreement</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
{% endblock content %}
Loading