Repository: CTFd/CTFd

Issue:
Create admin panel only scripts + stylesheet plugin interfaces
```
def register_script(url):
    SCRIPTS.append(url)


def register_stylesheet(url):
    STYLESHEETS.append(url)


def get_registered_scripts():
    return SCRIPTS


def get_registered_stylesheets():
    return STYLESHEETS
```

The above code exists for themes but the admin panel doesn't make use of it and it really shouldn't. We should make functions to add things specifically to the admin panel. 


Discussion:

Resolving patch:
diff --git a/CTFd/plugins/__init__.py b/CTFd/plugins/__init__.py
--- a/CTFd/plugins/__init__.py
+++ b/CTFd/plugins/__init__.py
@@ -3,20 +3,19 @@
 import os
 
 from collections import namedtuple
-from flask.helpers import safe_join
 from flask import current_app as app, send_file, send_from_directory, abort
 from CTFd.utils.decorators import admins_only as admins_only_wrapper
 from CTFd.utils.plugins import (
     override_template as utils_override_template,
     register_script as utils_register_plugin_script,
     register_stylesheet as utils_register_plugin_stylesheet,
+    register_admin_script as utils_register_admin_plugin_script,
+    register_admin_stylesheet as utils_register_admin_plugin_stylesheet
 )
 from CTFd.utils.config.pages import get_pages
 
 
 Menu = namedtuple('Menu', ['title', 'route'])
-ADMIN_PLUGIN_MENU_BAR = []
-USER_PAGE_MENU_BAR = []
 
 
 def register_plugin_assets_directory(app, base_path, admins_only=False):
@@ -83,6 +82,26 @@ def register_plugin_stylesheet(*args, **kwargs):
     utils_register_plugin_stylesheet(*args, **kwargs)
 
 
+def register_admin_plugin_script(*args, **kwargs):
+    """
+    Adds a given script to the base.html of the admin theme which all admin pages inherit from
+    :param args:
+    :param kwargs:
+    :return:
+    """
+    utils_register_admin_plugin_script(*args, **kwargs)
+
+
+def register_admin_plugin_stylesheet(*args, **kwargs):
+    """
+    Adds a given stylesheet to the base.html of the admin theme which all admin pages inherit from
+    :param args:
+    :param kwargs:
+    :return:
+    """
+    utils_register_admin_plugin_stylesheet(*args, **kwargs)
+
+
 def register_admin_plugin_menu_bar(title, route):
     """
     Registers links on the Admin Panel menubar/navbar
@@ -92,7 +111,7 @@ def register_admin_plugin_menu_bar(title, route):
     :return:
     """
     am = Menu(title=title, route=route)
-    ADMIN_PLUGIN_MENU_BAR.append(am)
+    app.admin_plugin_menu_bar.append(am)
 
 
 def get_admin_plugin_menu_bar():
@@ -101,7 +120,7 @@ def get_admin_plugin_menu_bar():
 
     :return: Returns a list of Menu namedtuples. They have name, and route attributes.
     """
-    return ADMIN_PLUGIN_MENU_BAR
+    return app.admin_plugin_menu_bar
 
 
 def register_user_page_menu_bar(title, route):
@@ -113,7 +132,7 @@ def register_user_page_menu_bar(title, route):
     :return:
     """
     p = Menu(title=title, route=route)
-    USER_PAGE_MENU_BAR.append(p)
+    app.plugin_menu_bar.append(p)
 
 
 def get_user_page_menu_bar():
@@ -122,7 +141,7 @@ def get_user_page_menu_bar():
 
     :return: Returns a list of Menu namedtuples. They have name, and route attributes.
     """
-    return get_pages() + USER_PAGE_MENU_BAR
+    return get_pages() + app.plugin_menu_bar
 
 
 def bypass_csrf_protection(f):
@@ -146,6 +165,14 @@ def init_plugins(app):
     :param app: A CTFd application
     :return:
     """
+    app.admin_plugin_scripts = []
+    app.admin_plugin_stylesheets = []
+    app.plugin_scripts = []
+    app.plugin_stylesheets = []
+
+    app.admin_plugin_menu_bar = []
+    app.plugin_menu_bar = []
+
     if app.config.get('SAFE_MODE', False) is False:
         modules = sorted(glob.glob(os.path.dirname(__file__) + "/*"))
         blacklist = {'__pycache__'}
diff --git a/CTFd/utils/initialization/__init__.py b/CTFd/utils/initialization/__init__.py
--- a/CTFd/utils/initialization/__init__.py
+++ b/CTFd/utils/initialization/__init__.py
@@ -8,7 +8,13 @@
 from CTFd.utils.config import can_send_mail, ctf_logo, ctf_name, ctf_theme
 from CTFd.utils.config.pages import get_pages
 
-from CTFd.utils.plugins import get_registered_stylesheets, get_registered_scripts, get_configurable_plugins
+from CTFd.utils.plugins import (
+    get_registered_stylesheets,
+    get_registered_scripts,
+    get_configurable_plugins,
+    get_registered_admin_scripts,
+    get_registered_admin_stylesheets
+)
 
 from CTFd.utils.countries import get_countries, lookup_country_code
 from CTFd.utils.user import authed, get_ip, get_current_user, get_current_team
@@ -45,6 +51,8 @@ def init_template_globals(app):
     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_registered_admin_scripts=get_registered_admin_scripts)
+    app.jinja_env.globals.update(get_registered_admin_stylesheets=get_registered_admin_stylesheets)
     app.jinja_env.globals.update(get_config=get_config)
     app.jinja_env.globals.update(generate_account_url=generate_account_url)
     app.jinja_env.globals.update(get_countries=get_countries)
diff --git a/CTFd/utils/plugins/__init__.py b/CTFd/utils/plugins/__init__.py
--- a/CTFd/utils/plugins/__init__.py
+++ b/CTFd/utils/plugins/__init__.py
@@ -4,24 +4,36 @@
 import json
 
 
-SCRIPTS = []
-STYLESHEETS = []
-
-
 def register_script(url):
-    SCRIPTS.append(url)
+    app.plugin_scripts.append(url)
 
 
 def register_stylesheet(url):
-    STYLESHEETS.append(url)
+    app.plugin_stylesheets.append(url)
+
+
+def register_admin_script(url):
+    app.admin_plugin_scripts.append(url)
+
+
+def register_admin_stylesheet(url):
+    app.admin_plugin_stylesheets.append(url)
 
 
 def get_registered_scripts():
-    return SCRIPTS
+    return app.plugin_scripts
 
 
 def get_registered_stylesheets():
-    return STYLESHEETS
+    return app.plugin_stylesheets
+
+
+def get_registered_admin_scripts():
+    return app.admin_plugin_scripts
+
+
+def get_registered_admin_stylesheets():
+    return app.admin_plugin_stylesheets
 
 
 def override_template(template, html):


Test patch:
diff --git a/tests/test_plugin_utils.py b/tests/test_plugin_utils.py
--- a/tests/test_plugin_utils.py
+++ b/tests/test_plugin_utils.py
@@ -7,6 +7,8 @@
     register_plugin_asset,
     register_plugin_script,
     register_plugin_stylesheet,
+    register_admin_plugin_script,
+    register_admin_plugin_stylesheet,
     override_template,
     register_admin_plugin_menu_bar,
     get_admin_plugin_menu_bar,
@@ -77,7 +79,7 @@ def test_admin_override_template():
 
 
 def test_register_plugin_script():
-    '''Test that register_plugin_script adds script paths to the core theme when used from a plugin'''
+    """Test that register_plugin_script adds script paths to the core theme when used from a plugin"""
     app = create_ctfd()
     with app.app_context():
         register_plugin_script('/fake/script/path.js')
@@ -91,7 +93,7 @@ def test_register_plugin_script():
 
 
 def test_register_plugin_stylesheet():
-    '''Test that register_plugin_stylesheet adds stylesheet paths to the core theme when used from a plugin'''
+    """Test that register_plugin_stylesheet adds stylesheet paths to the core theme when used from a plugin"""
     app = create_ctfd()
     with app.app_context():
         register_plugin_script('/fake/stylesheet/path.css')
@@ -104,6 +106,34 @@ def test_register_plugin_stylesheet():
     destroy_ctfd(app)
 
 
+def test_register_admin_plugin_script():
+    """Test that register_admin_plugin_script adds script paths to the admin theme when used from a plugin"""
+    app = create_ctfd()
+    with app.app_context():
+        register_admin_plugin_script('/fake/script/path.js')
+        register_admin_plugin_script('http://ctfd.io/fake/script/path.js')
+        with login_as_user(app, name="admin") as client:
+            r = client.get('/admin/statistics')
+            output = r.get_data(as_text=True)
+            assert '/fake/script/path.js' in output
+            assert 'http://ctfd.io/fake/script/path.js' in output
+    destroy_ctfd(app)
+
+
+def test_register_admin_plugin_stylesheet():
+    """Test that register_admin_plugin_stylesheet adds stylesheet paths to the admin theme when used from a plugin"""
+    app = create_ctfd()
+    with app.app_context():
+        register_admin_plugin_stylesheet('/fake/stylesheet/path.css')
+        register_admin_plugin_stylesheet('http://ctfd.io/fake/stylesheet/path.css')
+        with login_as_user(app, name="admin") as client:
+            r = client.get('/admin/statistics')
+            output = r.get_data(as_text=True)
+            assert '/fake/stylesheet/path.css' in output
+            assert 'http://ctfd.io/fake/stylesheet/path.css' in output
+    destroy_ctfd(app)
+
+
 def test_register_admin_plugin_menu_bar():
     """
     Test that register_admin_plugin_menu_bar() properly inserts into HTML and get_admin_plugin_menu_bar()