|
|
|
@@ -269,40 +269,6 @@ def _get_csrf_token():
|
|
|
|
|
def _is_csrf_exempt_request():
|
|
|
|
|
return request.method in {'GET', 'HEAD', 'OPTIONS', 'TRACE'}
|
|
|
|
|
|
|
|
|
|
def _is_library_module_path(path):
|
|
|
|
|
"""Return True when the current request path belongs to the library module."""
|
|
|
|
|
if not path:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
library_prefixes = (
|
|
|
|
|
'/library',
|
|
|
|
|
'/library_',
|
|
|
|
|
'/student_cards',
|
|
|
|
|
)
|
|
|
|
|
return path.startswith(library_prefixes)
|
|
|
|
|
|
|
|
|
|
def _is_inventory_module_path(path):
|
|
|
|
|
"""Return True when the current request path belongs to the inventory module."""
|
|
|
|
|
if not path:
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
if path == '/' or path.startswith('/home'):
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
inventory_prefixes = (
|
|
|
|
|
'/scanner',
|
|
|
|
|
'/inventory',
|
|
|
|
|
'/upload_admin',
|
|
|
|
|
'/manage_filters',
|
|
|
|
|
'/manage_locations',
|
|
|
|
|
'/admin_borrowings',
|
|
|
|
|
'/admin_damaged_items',
|
|
|
|
|
'/admin/borrowings',
|
|
|
|
|
'/admin/damaged_items',
|
|
|
|
|
'/terminplan',
|
|
|
|
|
)
|
|
|
|
|
return path.startswith(inventory_prefixes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@app.before_request
|
|
|
|
|
def _enforce_csrf_protection():
|
|
|
|
@@ -390,11 +356,14 @@ def _enforce_module_access():
|
|
|
|
|
if endpoint == 'static' or endpoint.startswith('static') or request.path.startswith('/api/'):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(request.path):
|
|
|
|
|
return "Bibliotheks-Modul ist deaktiviert.", 403
|
|
|
|
|
|
|
|
|
|
if not cfg.INVENTORY_MODULE_ENABLED and _is_inventory_module_path(request.path):
|
|
|
|
|
return "Inventar-Modul ist deaktiviert.", 403
|
|
|
|
|
for name, matcher in cfg.MODULES._path_matchers.items():
|
|
|
|
|
if matcher(request.path) and not cfg.MODULES.is_enabled(name):
|
|
|
|
|
msg = {
|
|
|
|
|
'library': "Bibliotheks-Modul ist deaktiviert.",
|
|
|
|
|
'inventory': "Inventar-Modul ist deaktiviert.",
|
|
|
|
|
'student_cards': "Schülerausweis-Modul ist deaktiviert."
|
|
|
|
|
}.get(name, f"{name.capitalize()}-Modul ist deaktiviert.")
|
|
|
|
|
return msg, 403
|
|
|
|
|
|
|
|
|
|
""" -----------------------------------------------------------After Request Handlers----------------------------------------------------------------------------- """
|
|
|
|
|
|
|
|
|
@@ -440,24 +409,18 @@ def _csrf_error_response(message='CSRF token fehlt oder ist ungültig.'):
|
|
|
|
|
|
|
|
|
|
def _get_current_module(path):
|
|
|
|
|
"""Resolve the active UI module for navbar separation."""
|
|
|
|
|
|
|
|
|
|
if cfg.LIBRARY_MODULE_ENABLED and _is_library_module_path(path):
|
|
|
|
|
session['last_module'] = 'library'
|
|
|
|
|
return 'library'
|
|
|
|
|
if cfg.INVENTORY_MODULE_ENABLED and _is_inventory_module_path(path):
|
|
|
|
|
session['last_module'] = 'inventory'
|
|
|
|
|
return 'inventory'
|
|
|
|
|
mod = cfg.MODULES.get_module_for_path(path)
|
|
|
|
|
if mod:
|
|
|
|
|
session['last_module'] = mod
|
|
|
|
|
return mod
|
|
|
|
|
|
|
|
|
|
last_module = session.get('last_module')
|
|
|
|
|
if last_module == 'library' and cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
return 'library'
|
|
|
|
|
if last_module == 'inventory' and cfg.INVENTORY_MODULE_ENABLED:
|
|
|
|
|
return 'inventory'
|
|
|
|
|
if last_module and cfg.MODULES.is_enabled(last_module):
|
|
|
|
|
return last_module
|
|
|
|
|
|
|
|
|
|
# Default fallback: prefer inventory if enabled, otherwise library, else inventory
|
|
|
|
|
if cfg.INVENTORY_MODULE_ENABLED:
|
|
|
|
|
if cfg.MODULES.is_enabled('inventory'):
|
|
|
|
|
return 'inventory'
|
|
|
|
|
return 'library' if cfg.LIBRARY_MODULE_ENABLED else 'inventory'
|
|
|
|
|
return 'library' if cfg.MODULES.is_enabled('library') else 'inventory'
|
|
|
|
|
|
|
|
|
|
"""---------------------------------------------User Access Permissions----------------------------------------------------------------------------- """
|
|
|
|
|
|
|
|
|
@@ -1063,9 +1026,9 @@ def inject_version():
|
|
|
|
|
'csrf_token': csrf_token,
|
|
|
|
|
'CURRENT_MODULE': current_module,
|
|
|
|
|
'school_periods': SCHOOL_PERIODS,
|
|
|
|
|
'inventory_module_enabled': cfg.INVENTORY_MODULE_ENABLED,
|
|
|
|
|
'library_module_enabled': cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
'student_cards_module_enabled': cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
'inventory_module_enabled': cfg.MODULES.is_enabled('inventory'),
|
|
|
|
|
'library_module_enabled': cfg.MODULES.is_enabled('library'),
|
|
|
|
|
'student_cards_module_enabled': cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
'is_admin': is_admin,
|
|
|
|
|
'unread_notification_count': unread_notification_count,
|
|
|
|
|
'current_permissions': current_permissions,
|
|
|
|
@@ -1641,6 +1604,20 @@ def _load_tabular_upload(uploaded_file):
|
|
|
|
|
workbook.close()
|
|
|
|
|
return header_row, data_rows
|
|
|
|
|
|
|
|
|
|
def _deny_if_unauthenticated_file_access():
|
|
|
|
|
"""Block file-serving routes unless a user is logged in."""
|
|
|
|
|
if 'username' not in session:
|
|
|
|
|
return Response('Forbidden', status=403)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _student_card_id_slug(value):
|
|
|
|
|
"""Build a compact identifier fragment from a name or class value."""
|
|
|
|
|
normalized = _normalize_excel_header(value)
|
|
|
|
|
if not normalized:
|
|
|
|
|
return ''
|
|
|
|
|
return re.sub(r'[^a-z0-9]+', '', normalized).upper()
|
|
|
|
|
|
|
|
|
|
"""-------------------------------------------------------------Filter----------------------------------------------------------------------------- """
|
|
|
|
|
FILTER_SELECT_ALL_TOKEN = '__ALL__'
|
|
|
|
|
|
|
|
|
@@ -1701,36 +1678,10 @@ def _is_public_host(hostname):
|
|
|
|
|
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local or ip_obj.is_multicast or ip_obj.is_reserved or ip_obj.is_unspecified:
|
|
|
|
|
return False
|
|
|
|
|
public_seen = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return public_seen
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _deny_if_unauthenticated_file_access():
|
|
|
|
|
"""Block file-serving routes unless a user is logged in."""
|
|
|
|
|
if 'username' not in session:
|
|
|
|
|
return Response('Forbidden', status=403)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _student_card_id_slug(value):
|
|
|
|
|
"""Build a compact identifier fragment from a name or class value."""
|
|
|
|
|
normalized = _normalize_excel_header(value)
|
|
|
|
|
if not normalized:
|
|
|
|
|
return ''
|
|
|
|
|
return re.sub(r'[^a-z0-9]+', '', normalized).upper()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _name_to_alias(full_name):
|
|
|
|
|
"""Convert clear names to deterministic aliases, e.g. Simon Frings -> SimFri."""
|
|
|
|
|
text = sanitize_form_value(full_name)
|
|
|
|
|
if not text:
|
|
|
|
|
return 'User'
|
|
|
|
|
|
|
|
|
|
parts = [p for p in re.split(r'\s+', text) if p]
|
|
|
|
|
if len(parts) >= 2:
|
|
|
|
|
return us.build_name_synonym(parts[0], parts[-1])
|
|
|
|
|
return us.build_name_synonym(parts[0], '')
|
|
|
|
|
|
|
|
|
|
"""-------------------------------------------------------------Student Cards Excel Import----------------------------------------------------------------------------- """
|
|
|
|
|
|
|
|
|
|
def _build_student_card_excel_id(student_name, class_name, row_number, used_ids):
|
|
|
|
|
"""Create a stable student-card ID without embedding personal names."""
|
|
|
|
@@ -1762,7 +1713,7 @@ def _upload_student_cards_excel():
|
|
|
|
|
flash('Administratorrechte erforderlich.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
|
if not cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
flash('Schülerausweis-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -1858,8 +1809,6 @@ def _upload_student_cards_excel():
|
|
|
|
|
student_name = f'{first_name} {last_name}'.strip()
|
|
|
|
|
validation_warnings.append((row_number, 'Schülername wurde aus Vorname und Nachname zusammengesetzt'))
|
|
|
|
|
|
|
|
|
|
student_name_alias = _name_to_alias(student_name)
|
|
|
|
|
|
|
|
|
|
if not ausweis_id and not student_name and not class_name:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
@@ -1884,7 +1833,7 @@ def _upload_student_cards_excel():
|
|
|
|
|
planned_rows.append({
|
|
|
|
|
'row_number': row_number,
|
|
|
|
|
'ausweis_id': ausweis_id,
|
|
|
|
|
'student_name': student_name_alias,
|
|
|
|
|
'student_name': student_name,
|
|
|
|
|
'class_name': class_name,
|
|
|
|
|
'notes': notes,
|
|
|
|
|
'default_borrow_days': default_borrow_days,
|
|
|
|
@@ -1959,7 +1908,7 @@ def _upload_excel_items(scope='inventory'):
|
|
|
|
|
fallback_route = 'library_admin' if is_library_scope else 'upload_admin'
|
|
|
|
|
|
|
|
|
|
if is_library_scope:
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home'))
|
|
|
|
|
|
|
|
|
@@ -2290,6 +2239,7 @@ def upload_student_cards_excel():
|
|
|
|
|
"""Bulk import student cards from Excel."""
|
|
|
|
|
return _upload_student_cards_excel()
|
|
|
|
|
|
|
|
|
|
"""-------------------------------------------------------------File Serving-----------------------------------------------------------------------------"""
|
|
|
|
|
|
|
|
|
|
@app.route('/uploads/<filename>')
|
|
|
|
|
def uploaded_file(filename):
|
|
|
|
@@ -2581,27 +2531,11 @@ def catch_all_files(filename):
|
|
|
|
|
os.path.join(BASE_DIR, 'static')
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# Check development paths first
|
|
|
|
|
for directory in possible_dirs:
|
|
|
|
|
file_path = os.path.join(directory, filename)
|
|
|
|
|
if os.path.isfile(file_path):
|
|
|
|
|
return send_from_directory(directory, os.path.basename(filename))
|
|
|
|
|
|
|
|
|
|
# Check production paths if available
|
|
|
|
|
if os.path.exists("/var/Inventarsystem/Web"):
|
|
|
|
|
prod_dirs = [
|
|
|
|
|
"/var/Inventarsystem/Web/uploads",
|
|
|
|
|
"/var/Inventarsystem/Web/thumbnails",
|
|
|
|
|
"/var/Inventarsystem/Web/previews",
|
|
|
|
|
# "/var/Inventarsystem/Web/QRCodes", # QR Code serving deactivated
|
|
|
|
|
"/var/Inventarsystem/Web/static"
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for directory in prod_dirs:
|
|
|
|
|
file_path = os.path.join(directory, filename)
|
|
|
|
|
if os.path.isfile(file_path):
|
|
|
|
|
return send_from_directory(directory, os.path.basename(filename))
|
|
|
|
|
|
|
|
|
|
# Check if this looks like an image request
|
|
|
|
|
if any(filename.lower().endswith(ext) for ext in ['png', 'jpg', 'jpeg', 'gif', 'svg']):
|
|
|
|
|
# Use a placeholder image if file not found - first try SVG, then PNG
|
|
|
|
@@ -2621,6 +2555,7 @@ def catch_all_files(filename):
|
|
|
|
|
print(f"Error in catch-all route for {filename}: {str(e)}")
|
|
|
|
|
return Response(f"Error serving file: {str(e)}", status=500)
|
|
|
|
|
|
|
|
|
|
"""-------------------------------------------------------------Main Views-----------------------------------------------------------------------------"""
|
|
|
|
|
|
|
|
|
|
@app.route('/test_connection', methods=['GET'])
|
|
|
|
|
def test_connection():
|
|
|
|
@@ -2671,8 +2606,8 @@ def home():
|
|
|
|
|
flash('Bitte mit registriertem Konto anmelden!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
if not cfg.INVENTORY_MODULE_ENABLED:
|
|
|
|
|
if cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('inventory'):
|
|
|
|
|
if cfg.MODULES.is_enabled('library'):
|
|
|
|
|
return redirect(url_for('library_view'))
|
|
|
|
|
else:
|
|
|
|
|
return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403
|
|
|
|
@@ -2681,8 +2616,8 @@ def home():
|
|
|
|
|
return render_template(
|
|
|
|
|
'main.html',
|
|
|
|
|
username=session['username'],
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
|
|
|
|
|
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
|
|
|
|
|
)
|
|
|
|
@@ -2710,8 +2645,8 @@ def home_admin():
|
|
|
|
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
|
|
|
|
|
if not cfg.INVENTORY_MODULE_ENABLED:
|
|
|
|
|
if cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('inventory'):
|
|
|
|
|
if cfg.MODULES.is_enabled('library'):
|
|
|
|
|
return redirect(url_for('library_admin'))
|
|
|
|
|
else:
|
|
|
|
|
return "Weder Inventar- noch Bibliotheks-Modul sind aktiviert.", 403
|
|
|
|
@@ -2722,8 +2657,8 @@ def home_admin():
|
|
|
|
|
return render_template(
|
|
|
|
|
'main_admin.html',
|
|
|
|
|
username=session['username'],
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
|
|
|
|
|
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS,
|
|
|
|
|
school_info=_get_school_info_for_export(),
|
|
|
|
@@ -2741,8 +2676,8 @@ def tutorial_page():
|
|
|
|
|
'tutorial.html',
|
|
|
|
|
username=session['username'],
|
|
|
|
|
is_admin=us.check_admin(session['username']),
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
|
|
|
|
|
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
|
|
|
|
|
)
|
|
|
|
@@ -2760,16 +2695,16 @@ def library_view():
|
|
|
|
|
if 'username' not in session:
|
|
|
|
|
flash('Bitte mit registriertem Konto anmelden!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home'))
|
|
|
|
|
|
|
|
|
|
return render_template(
|
|
|
|
|
'library_table.html',
|
|
|
|
|
username=session['username'],
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
is_admin=us.check_admin(session['username']),
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
|
|
|
|
|
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
|
|
|
|
|
)
|
|
|
|
@@ -2784,7 +2719,7 @@ def library_loans_admin():
|
|
|
|
|
if not us.check_admin(session['username']):
|
|
|
|
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -2885,8 +2820,8 @@ def library_loans_admin():
|
|
|
|
|
'library_borrowings_admin.html',
|
|
|
|
|
loan_entries=loan_entries,
|
|
|
|
|
damaged_items=damaged_items,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
app.logger.error(f"Error loading library loans admin view: {e}")
|
|
|
|
@@ -3021,9 +2956,9 @@ def api_library_scan_action():
|
|
|
|
|
"""
|
|
|
|
|
if 'username' not in session:
|
|
|
|
|
return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403
|
|
|
|
|
if not cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
return jsonify({'ok': False, 'message': 'Schülerausweis-Modul ist deaktiviert.'}), 403
|
|
|
|
|
|
|
|
|
|
payload = request.get_json(silent=True) or {}
|
|
|
|
@@ -3227,7 +3162,7 @@ def api_library_item_update(item_id):
|
|
|
|
|
return jsonify({'ok': False, 'message': 'Nicht angemeldet.'}), 401
|
|
|
|
|
if not us.check_admin(session['username']):
|
|
|
|
|
return jsonify({'ok': False, 'message': 'Administratorrechte erforderlich.'}), 403
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
return jsonify({'ok': False, 'message': 'Bibliotheks-Modul ist deaktiviert.'}), 403
|
|
|
|
|
|
|
|
|
|
payload = request.get_json(silent=True) or {}
|
|
|
|
@@ -3358,8 +3293,8 @@ def upload_admin():
|
|
|
|
|
'upload_admin.html',
|
|
|
|
|
username=session['username'],
|
|
|
|
|
duplicate_data=duplicate_data,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
show_library_features=False,
|
|
|
|
|
upload_mode='item',
|
|
|
|
|
page_title='Artikel hochladen',
|
|
|
|
@@ -3380,7 +3315,7 @@ def library_admin():
|
|
|
|
|
if not _action_access_allowed(permissions, 'can_insert'):
|
|
|
|
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -3424,8 +3359,8 @@ def library_admin():
|
|
|
|
|
'upload_admin.html',
|
|
|
|
|
username=session['username'],
|
|
|
|
|
duplicate_data=duplicate_data,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
show_library_features=True,
|
|
|
|
|
upload_mode='library',
|
|
|
|
|
page_title='Bücher hochladen',
|
|
|
|
@@ -3445,7 +3380,7 @@ def student_cards_admin():
|
|
|
|
|
if not us.check_admin(session['username']):
|
|
|
|
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
flash('Schülerausweis-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -3481,7 +3416,7 @@ def student_cards_admin():
|
|
|
|
|
action = request.form.get('action', 'add')
|
|
|
|
|
ausweis_id = request.form.get('ausweis_id', '').strip().upper()
|
|
|
|
|
student_name = request.form.get('student_name', '').strip()
|
|
|
|
|
student_name_alias = _name_to_alias(student_name)
|
|
|
|
|
student_name_alias = student_name
|
|
|
|
|
default_borrow_days = request.form.get('default_borrow_days', 14)
|
|
|
|
|
class_name = request.form.get('class_name', '').strip()
|
|
|
|
|
notes = request.form.get('notes', '').strip()
|
|
|
|
@@ -3571,8 +3506,8 @@ def student_cards_admin():
|
|
|
|
|
edit_mode=edit_mode,
|
|
|
|
|
form_data=form_data,
|
|
|
|
|
config={'default': cfg.STUDENT_DEFAULT_BORROW_DAYS},
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -3588,7 +3523,7 @@ def student_cards_print():
|
|
|
|
|
if not us.check_admin(session['username']):
|
|
|
|
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
flash('Schülerausweis-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -3620,7 +3555,7 @@ def student_card_barcode_print():
|
|
|
|
|
if not us.check_admin(session['username']):
|
|
|
|
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
flash('Schülerausweis-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -3651,7 +3586,7 @@ def student_card_barcode_download():
|
|
|
|
|
if not us.check_admin(session['username']):
|
|
|
|
|
flash('Zugriff verweigert.', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
flash('Schülerausweis-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -3825,7 +3760,7 @@ def student_card_single_barcode_download(card_id):
|
|
|
|
|
if not us.check_admin(session['username']):
|
|
|
|
|
flash('Zugriff verweigert.', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
flash('Schülerausweis-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -4694,7 +4629,7 @@ def upload_item():
|
|
|
|
|
|
|
|
|
|
item_isbn = ''
|
|
|
|
|
item_type = 'general'
|
|
|
|
|
if cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if cfg.MODULES.is_enabled('library'):
|
|
|
|
|
item_isbn = normalize_and_validate_isbn(isbn_raw)
|
|
|
|
|
if isbn_raw and not item_isbn:
|
|
|
|
|
error_msg = 'Ungültige ISBN. Bitte ISBN-10 oder ISBN-13 verwenden.'
|
|
|
|
@@ -4706,7 +4641,7 @@ def upload_item():
|
|
|
|
|
item_type = 'book'
|
|
|
|
|
|
|
|
|
|
if upload_mode == 'library':
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
error_msg = 'Bibliotheks-Modul ist deaktiviert.'
|
|
|
|
|
if is_mobile:
|
|
|
|
|
return jsonify({'success': False, 'message': error_msg}), 400
|
|
|
|
@@ -4736,6 +4671,10 @@ def upload_item():
|
|
|
|
|
if is_mobile:
|
|
|
|
|
return jsonify({'success': False, 'message': error_msg}), 400
|
|
|
|
|
else:
|
|
|
|
|
conflicting_items = it.get_item_by_code_4(code_4[0])
|
|
|
|
|
if conflicting_items:
|
|
|
|
|
flash('Der Code wird bereits verwendet. Das existierende Element wurde geöffnet.', 'info')
|
|
|
|
|
return redirect(url_for(success_redirect_endpoint, open_item_modal=str(conflicting_items[0]['_id'])))
|
|
|
|
|
flash(error_msg, 'error')
|
|
|
|
|
return redirect(url_for(success_redirect_endpoint))
|
|
|
|
|
|
|
|
|
@@ -4760,6 +4699,10 @@ def upload_item():
|
|
|
|
|
error_msg = f'Der Einzelcode "{specific_code}" wird bereits verwendet.'
|
|
|
|
|
if is_mobile:
|
|
|
|
|
return jsonify({'success': False, 'message': error_msg}), 400
|
|
|
|
|
conflicting_items = it.get_item_by_code_4(specific_code)
|
|
|
|
|
if conflicting_items:
|
|
|
|
|
flash(f'Der Einzelcode "{specific_code}" wird bereits verwendet. Das existierende Element wurde geöffnet.', 'info')
|
|
|
|
|
return redirect(url_for(success_redirect_endpoint, open_item_modal=str(conflicting_items[0]['_id'])))
|
|
|
|
|
flash(error_msg, 'error')
|
|
|
|
|
return redirect(url_for(success_redirect_endpoint))
|
|
|
|
|
|
|
|
|
@@ -5881,7 +5824,7 @@ def edit_item(id):
|
|
|
|
|
|
|
|
|
|
item_isbn = ''
|
|
|
|
|
item_type = 'general'
|
|
|
|
|
if cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if cfg.MODULES.is_enabled('library'):
|
|
|
|
|
item_isbn = normalize_and_validate_isbn(isbn_raw)
|
|
|
|
|
if isbn_raw and not item_isbn:
|
|
|
|
|
flash('Ungültige ISBN. Bitte ISBN-10 oder ISBN-13 verwenden.', 'error')
|
|
|
|
@@ -5891,6 +5834,10 @@ def edit_item(id):
|
|
|
|
|
|
|
|
|
|
# Check if code is unique (excluding the current item)
|
|
|
|
|
if code_4 and not it.is_code_unique(code_4, exclude_id=id):
|
|
|
|
|
conflicting_items = it.get_item_by_code_4(code_4)
|
|
|
|
|
if conflicting_items:
|
|
|
|
|
flash('Der Code wird bereits verwendet. Das existierende Element wurde stattdessen geöffnet.', 'info')
|
|
|
|
|
return redirect(url_for('home_admin', open_item_modal=str(conflicting_items[0]['_id'])))
|
|
|
|
|
flash('Der Code wird bereits verwendet. Bitte wählen Sie einen anderen Code.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -6164,7 +6111,7 @@ def ausleihen(id):
|
|
|
|
|
|
|
|
|
|
username = session['username']
|
|
|
|
|
requested_return_to = (request.form.get('return_to') or '').strip().lower()
|
|
|
|
|
if requested_return_to == 'library' and cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if requested_return_to == 'library' and cfg.MODULES.is_enabled('library'):
|
|
|
|
|
redirect_target = 'library_view'
|
|
|
|
|
else:
|
|
|
|
|
redirect_target = 'home_admin' if us.check_admin(username) else 'home'
|
|
|
|
@@ -6182,7 +6129,7 @@ def ausleihen(id):
|
|
|
|
|
|
|
|
|
|
# Library media can only be borrowed with a valid student card.
|
|
|
|
|
if is_library_item:
|
|
|
|
|
if not cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
flash('Bibliotheksmedien können nur mit aktivem Schülerausweis-Modul ausgeliehen werden.', 'error')
|
|
|
|
|
return redirect(url_for(redirect_target))
|
|
|
|
|
if not student_card_id:
|
|
|
|
@@ -6211,7 +6158,7 @@ def ausleihen(id):
|
|
|
|
|
if client:
|
|
|
|
|
client.close()
|
|
|
|
|
|
|
|
|
|
if cfg.STUDENT_CARDS_MODULE_ENABLED:
|
|
|
|
|
if cfg.MODULES.is_enabled('student_cards'):
|
|
|
|
|
duration_raw = (request.form.get('borrow_duration_days') or '').strip()
|
|
|
|
|
if duration_raw:
|
|
|
|
|
try:
|
|
|
|
@@ -7131,8 +7078,8 @@ def register():
|
|
|
|
|
return redirect(url_for('home'))
|
|
|
|
|
return render_template(
|
|
|
|
|
'register.html',
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
student_default_borrow_days=cfg.STUDENT_DEFAULT_BORROW_DAYS,
|
|
|
|
|
student_max_borrow_days=cfg.STUDENT_MAX_BORROW_DAYS
|
|
|
|
|
)
|
|
|
|
@@ -7198,8 +7145,8 @@ def user_del():
|
|
|
|
|
return render_template(
|
|
|
|
|
'user_del.html',
|
|
|
|
|
users=users_list,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -7339,8 +7286,8 @@ def admin_borrowings():
|
|
|
|
|
return render_template(
|
|
|
|
|
'admin_borrowings.html',
|
|
|
|
|
entries=entries,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
"""-----------------------------------------------------------Audit Routes-------------------------------------------------------"""
|
|
|
|
@@ -7401,8 +7348,8 @@ def admin_audit_dashboard():
|
|
|
|
|
'admin_audit.html',
|
|
|
|
|
verify_result=verify_result,
|
|
|
|
|
audit_rows=audit_rows,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
app.logger.error(f"Error loading audit dashboard: {exc}")
|
|
|
|
@@ -8148,7 +8095,7 @@ def library_item_invoices(item_id):
|
|
|
|
|
if 'username' not in session or not us.check_admin(session['username']):
|
|
|
|
|
flash('Ihnen ist es nicht gestattet auf dieser Internetanwendung, die eben besuchte Adrrese zu nutzen, versuchen sie es erneut nach dem sie sich mit einem berechtigten Nutzer angemeldet haben!', 'error')
|
|
|
|
|
return redirect(url_for('login'))
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
flash('Bibliotheks-Modul ist deaktiviert.', 'error')
|
|
|
|
|
return redirect(url_for('home_admin'))
|
|
|
|
|
|
|
|
|
@@ -8216,8 +8163,8 @@ def library_item_invoices(item_id):
|
|
|
|
|
'isbn': item_doc.get('ISBN', ''),
|
|
|
|
|
},
|
|
|
|
|
invoices=entries,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
app.logger.error(f"Error loading invoice history for item {item_id}: {e}")
|
|
|
|
@@ -8365,13 +8312,12 @@ def admin_anonymize_names():
|
|
|
|
|
|
|
|
|
|
for card_doc in student_cards_col.find({}, {'SchülerName': 1, 'Klasse': 1, 'Notizen': 1}):
|
|
|
|
|
decrypted = _decrypt_student_card_doc(card_doc)
|
|
|
|
|
alias = _name_to_alias(decrypted.get('SchülerName', ''))
|
|
|
|
|
class_name = sanitize_form_value(decrypted.get('Klasse', ''))
|
|
|
|
|
notes = sanitize_form_value(decrypted.get('Notizen', ''))
|
|
|
|
|
|
|
|
|
|
encrypted_payload = encrypt_document_fields(
|
|
|
|
|
{
|
|
|
|
|
'SchülerName': alias,
|
|
|
|
|
'SchülerName': decrypted.get('SchülerName', ''),
|
|
|
|
|
'Klasse': class_name,
|
|
|
|
|
'Notizen': notes,
|
|
|
|
|
},
|
|
|
|
@@ -8544,8 +8490,8 @@ def logs():
|
|
|
|
|
return render_template(
|
|
|
|
|
'logs.html',
|
|
|
|
|
items=formatted_items,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -8610,8 +8556,8 @@ def manage_filters():
|
|
|
|
|
'manage_filters.html',
|
|
|
|
|
filter1_values=filter1_values,
|
|
|
|
|
filter2_values=filter2_values,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@app.route('/add_filter_value/<int:filter_num>', methods=['POST'])
|
|
|
|
@@ -8742,7 +8688,7 @@ def fetch_book_info(isbn):
|
|
|
|
|
if 'username' not in session or not us.check_admin(session['username']):
|
|
|
|
|
return jsonify({"error": "Not authorized"}), 403
|
|
|
|
|
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
return jsonify({"error": "Bibliotheks-Modul ist deaktiviert."}), 403
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
@@ -8837,7 +8783,7 @@ def download_book_cover():
|
|
|
|
|
return jsonify({"error": "Not authorized"}), 403
|
|
|
|
|
if not us.check_admin(session['username']):
|
|
|
|
|
return jsonify({"error": "Admin privileges required"}), 403
|
|
|
|
|
if not cfg.LIBRARY_MODULE_ENABLED:
|
|
|
|
|
if not cfg.MODULES.is_enabled('library'):
|
|
|
|
|
return jsonify({"error": "Bibliotheks-Modul ist deaktiviert."}), 403
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
@@ -9204,8 +9150,8 @@ def notifications_view():
|
|
|
|
|
user_notifications=user_notifications,
|
|
|
|
|
admin_notifications=admin_notifications,
|
|
|
|
|
is_admin_user=is_admin_user,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
app.logger.error(f"Error loading notifications: {exc}")
|
|
|
|
@@ -9436,8 +9382,8 @@ def admin_damaged_items():
|
|
|
|
|
return render_template(
|
|
|
|
|
'admin_damaged_items.html',
|
|
|
|
|
damaged_items=damaged_rows,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
)
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
app.logger.error(f"Error loading damaged-items admin view: {exc}")
|
|
|
|
@@ -9540,8 +9486,8 @@ def manage_locations():
|
|
|
|
|
return render_template(
|
|
|
|
|
'manage_locations.html',
|
|
|
|
|
location_values=location_values,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards')
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@@ -9649,8 +9595,8 @@ def admin_school_settings():
|
|
|
|
|
school_info=current_school,
|
|
|
|
|
tenant_id=tenant_id,
|
|
|
|
|
tenant_db=tenant_db,
|
|
|
|
|
library_module_enabled=cfg.LIBRARY_MODULE_ENABLED,
|
|
|
|
|
student_cards_module_enabled=cfg.STUDENT_CARDS_MODULE_ENABLED,
|
|
|
|
|
library_module_enabled=cfg.MODULES.is_enabled('library'),
|
|
|
|
|
student_cards_module_enabled=cfg.MODULES.is_enabled('student_cards'),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@app.route('/check_code_unique/<code>')
|
|
|
|
|