Repository: CTFd/CTFd
Pull request #375 (2017-09-04 09:54:11)

Plugins can register css files
Adds the ability for plugins to register CSS files so that they can change aesthetics. 


Patch:
diff --git a/CTFd/themes/original/templates/base.html b/CTFd/themes/original/templates/base.html
index 98c75f25..c0dc4bfa 100644
--- a/CTFd/themes/original/templates/base.html
+++ b/CTFd/themes/original/templates/base.html
@@ -13,6 +13,15 @@
 		<link rel="stylesheet" href="{{ request.script_root }}/themes/{{ ctf_theme() }}/static/css/style.css">
 		<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/static/user.css">
 		{% block stylesheets %}{% endblock %}
+		{% for stylesheet in get_registered_stylesheets() %}
+		{% if stylesheet.startswith('http') %}
+		<link rel="stylesheet" type="text/css" href="{{ stylesheet }}">
+		{% elif request.script_root %}
+		<link rel="stylesheet" type="text/css" href="{{ request.script_root }}/{{ stylesheet }}">
+		{% else %}
+		<link rel="stylesheet" type="text/css" href="{{ stylesheet }}">
+		{% endif %}
+		{% endfor %}
 		<script src="{{ request.script_root }}/themes/{{ ctf_theme() }}/static/js/vendor/moment.min.js"></script>
 		<script src="{{ request.script_root }}/themes/original/static/js/vendor/handlebars.min.js"></script>
 		<script type="text/javascript">
@@ -76,7 +85,13 @@
 	{% endblock %}
 
 	{% for script in get_registered_scripts() %}
-	<script src="{% if script.startswith('http')%}{{ request.script_root }}{% endif %}{{ script }}"></script>
+	{% if script.startswith('http') %}
+	<script src="{{ script }}"></script>
+	{% elif request.script_root %}
+	<script src="{{ request.script_root }}/{{ script }}"></script>
+	{% else %}
+	<script src="{{ script }}"></script>
+	{% endif %}
 	{% endfor %}
 </body>
 </html>
diff --git a/CTFd/utils.py b/CTFd/utils.py
index 8bc8b2ea..57a0f91d 100644
--- a/CTFd/utils.py
+++ b/CTFd/utils.py
@@ -35,6 +35,7 @@ cache = Cache()
 migrate = Migrate()
 markdown = mistune.Markdown()
 plugin_scripts = []
+plugin_stylesheets = []
 
 
 def init_logs(app):
@@ -108,6 +109,7 @@ def init_utils(app):
     app.jinja_env.globals.update(ctf_theme=ctf_theme)
     app.jinja_env.globals.update(get_configurable_plugins=get_configurable_plugins)
     app.jinja_env.globals.update(get_registered_scripts=get_registered_scripts)
+    app.jinja_env.globals.update(get_registered_stylesheets=get_registered_stylesheets)
     app.jinja_env.globals.update(get_config=get_config)
     app.jinja_env.globals.update(hide_scores=hide_scores)
 
@@ -182,6 +184,10 @@ def register_plugin_script(url):
     plugin_scripts.append(url)
 
 
+def register_plugin_stylesheet(url):
+    plugin_stylesheets.append(url)
+
+
 def pages():
     pages = Pages.query.filter(Pages.route != "index").all()
     return pages
@@ -359,6 +365,10 @@ def get_registered_scripts():
     return plugin_scripts
 
 
+def get_registered_stylesheets():
+    return plugin_stylesheets
+
+
 def upload_file(file, chalid):
     filename = secure_filename(file.filename)
 


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 6d5a86c0..efbf023e 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -4,6 +4,7 @@
 from tests.helpers import *
 from CTFd.models import ip2long, long2ip
 from CTFd.utils import get_config, set_config, override_template, sendmail, verify_email, ctf_started, ctf_ended
+from CTFd.utils import register_plugin_script, register_plugin_stylesheet
 from CTFd.utils import base64encode, base64decode
 from freezegun import freeze_time
 from mock import patch
@@ -172,7 +173,7 @@ def test_verify_email(mock_smtp):
 
 
 def test_ctftime_prevents_accessing_challenges_before_ctf():
-    """Test that the ctftime function prevents users from accessing challenges after the ctf"""
+    """Test that the ctftime function prevents users from accessing challenges before the ctf"""
     app = create_ctfd()
     with app.app_context():
         set_config('start', '1507089600')  # Wednesday, October 4, 2017 12:00:00 AM GMT-04:00 DST
@@ -293,3 +294,29 @@ def test_ctf_ended():
         with freeze_time("2017-10-7"):
             assert ctf_ended() == True
     destroy_ctfd(app)
+
+
+def test_register_plugin_script():
+    '''Test that register_plugin_script adds script paths to the original theme'''
+    app = create_ctfd()
+    with app.app_context():
+        register_plugin_script('/fake/script/path.js')
+        register_plugin_script('http://ctfd.io/fake/script/path.js')
+        with app.test_client() as client:
+            r = client.get('/')
+            output = r.get_data(as_text=True)
+            assert '/fake/script/path.js' in output
+            assert 'http://ctfd.io/fake/script/path.js' in output
+
+
+def test_register_plugin_stylesheet():
+    '''Test that register_plugin_stylesheet adds stylesheet paths to the original theme'''
+    app = create_ctfd()
+    with app.app_context():
+        register_plugin_script('/fake/stylesheet/path.css')
+        register_plugin_script('http://ctfd.io/fake/stylesheet/path.css')
+        with app.test_client() as client:
+            r = client.get('/')
+            output = r.get_data(as_text=True)
+            assert '/fake/stylesheet/path.css' in output
+            assert 'http://ctfd.io/fake/stylesheet/path.css' in output