diff --git a/.coveragerc b/.coveragerc index 1e4b063304..f3ec4743f8 100755 --- a/.coveragerc +++ b/.coveragerc @@ -1,12 +1,19 @@ [report] +# Add a column to the report with a summary of which lines (and branches) the tests missed. +# This makes it easy to go from a failure to fixing it, rather than using the HTML report. +show_missing = True -# nb: you can also add a "# pragma: no cover" -# on each function you don't want to be covered [run] relative_files = True source = . + +# This ensures that your code runs through both the True and False paths of each conditional statement. +# branch = True + # Here you can exclude a file from coverage testing +# nb: you can also add a "# pragma: no cover" +# on each function you don't want to be covered omit = pod/*settings*.py pod/custom/settings*.py pod/*apps.py diff --git a/.eslintrc.js b/.eslintrc.js index cb0dc50957..cc8bbe144a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,6 +30,8 @@ module.exports = { }, /* functions and Objects that will not trigger a "not defined" error. */ "globals": { + "require": true, + "process": true, "Cookies": "readonly", "gettext": "readonly", "ngettext": "readonly", @@ -41,5 +43,12 @@ module.exports = { "showalert": "writable", "showLoader": "writable", "manageDisableBtn": "writable" - } + }, + overrides: [ + { + // Allow use of import & export functions + files: [ "pod/main/static/js/utils.js", "pod/video/static/js/regroup_videos_by_theme.js" ], + parserOptions: { sourceType: "module" }, + } + ] }; diff --git a/.github/workflows/code_formatting.yml b/.github/workflows/code_formatting.yml index f8b37c1356..2a24b508e3 100644 --- a/.github/workflows/code_formatting.yml +++ b/.github/workflows/code_formatting.yml @@ -1,6 +1,5 @@ --- name: Code Formatting -run-name: ${{ github.actor }} is running Pod code formatting 🎨 on: push: diff --git a/.github/workflows/pod_dev.yml b/.github/workflows/pod_dev.yml index 519d71cee9..673d71af5b 100644 --- a/.github/workflows/pod_dev.yml +++ b/.github/workflows/pod_dev.yml @@ -56,18 +56,17 @@ jobs: - name: Install Dependencies run: | - sudo apt-get clean sudo apt-get update - sudo apt-get install ffmpeg - sudo apt-get install -y ffmpegthumbnailer + sudo apt-get install -y --no-install-recommends ffmpeg ffmpegthumbnailer + sudo apt-get clean + sudo rm -rf /var/lib/apt/lists/* python -m pip install --upgrade pip pip install -r requirements-dev.txt sudo npm install -g yarn - # FLAKE 8 + # FLAKE 8 (see setup.cfg for configurations) - name: Flake8 compliance - run: | - flake8 --max-complexity=7 --ignore=E501,W503,E203 --exclude .git,pod/*/migrations/*.py,test_settings.py,node_modules/*/*.py,pod/static/*.py,pod/custom/tenants/*/*.py + run: flake8 ## Start remote encoding and transcoding test ## - name: Create settings local file @@ -147,6 +146,12 @@ jobs: coverage run --append manage.py test -v 3 --settings=pod.main.test_settings coverage xml -o cobertura.xml + - name: Codacy coverage reporter + run: bash <(curl -Ls https://coverage.codacy.com/get.sh) + continue-on-error: true + env: + CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} + - name: Send coverage to coveralls.io # coveralls command has been installed by requirements-dev.txt env: diff --git a/.github/workflows/pod_main.yml b/.github/workflows/pod_main.yml index 6b85e3942f..b1ab94202c 100644 --- a/.github/workflows/pod_main.yml +++ b/.github/workflows/pod_main.yml @@ -45,11 +45,10 @@ jobs: - name: Install Dependencies run: | - sudo apt-get clean sudo apt-get update - sudo apt-get install ffmpeg - sudo apt-get install -y ffmpegthumbnailer - sudo apt-get install -y npm + sudo apt-get install -y --no-install-recommends ffmpeg ffmpegthumbnailer npm + sudo apt-get clean + sudo rm -rf /var/lib/apt/lists/* python -m pip install --upgrade pip pip install -r requirements-dev.txt sudo npm install -g yarn diff --git a/.gitignore b/.gitignore index 28bf70b800..bb869c0a8f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Compiled source # +# Compiled source # ################### *.com *.class @@ -60,6 +60,7 @@ chromedriver pod/static/ *.bak .coverage +.cache_ggshield htmlcov compile-model diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000000..68b5d5639c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/gitguardian/ggshield + rev: v1.27.0 + hooks: + - id: ggshield + language_version: python3 + stages: [commit] diff --git a/Makefile b/Makefile index c542406ed0..fe88dce444 100755 --- a/Makefile +++ b/Makefile @@ -67,7 +67,7 @@ migrate: # Launch all unit tests. tests: - coverage run --source='.' manage.py test --settings=pod.main.test_settings + coverage run manage.py test --settings=pod.main.test_settings coverage html # Ensure coherence of all code style diff --git a/dockerfile-dev-with-volumes/pod-back/Dockerfile b/dockerfile-dev-with-volumes/pod-back/Dockerfile index 99aed8134c..d092993fc2 100755 --- a/dockerfile-dev-with-volumes/pod-back/Dockerfile +++ b/dockerfile-dev-with-volumes/pod-back/Dockerfile @@ -19,7 +19,10 @@ FROM $PYTHON_VERSION # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update && apt-get install -y netcat && apt-get install -y gettext +RUN apt-get update \ + && apt-get install -y --no-install-recommends netcat gettext \ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/dockerfile-dev-with-volumes/pod-encode/Dockerfile b/dockerfile-dev-with-volumes/pod-encode/Dockerfile index 46b420eeb9..4ccf83f64d 100755 --- a/dockerfile-dev-with-volumes/pod-encode/Dockerfile +++ b/dockerfile-dev-with-volumes/pod-encode/Dockerfile @@ -16,11 +16,14 @@ COPY ./pod/ . # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update \ - && apt-get install -y netcat \ - ffmpeg \ - ffmpegthumbnailer \ - imagemagick +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + netcat \ + ffmpeg \ + ffmpegthumbnailer \ + imagemagick \ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/dockerfile-dev-with-volumes/pod-transcript/Dockerfile b/dockerfile-dev-with-volumes/pod-transcript/Dockerfile index 008f823cb0..a549ea5822 100755 --- a/dockerfile-dev-with-volumes/pod-transcript/Dockerfile +++ b/dockerfile-dev-with-volumes/pod-transcript/Dockerfile @@ -16,10 +16,13 @@ COPY ./pod/ . # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update \ - && apt-get install -y netcat \ - sox \ - libsox-fmt-mp3 +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + netcat \ + sox \ + libsox-fmt-mp3 \ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/dockerfile-dev-with-volumes/pod-xapi/Dockerfile b/dockerfile-dev-with-volumes/pod-xapi/Dockerfile index ef1da5e0d0..15e8e3f451 100755 --- a/dockerfile-dev-with-volumes/pod-xapi/Dockerfile +++ b/dockerfile-dev-with-volumes/pod-xapi/Dockerfile @@ -16,7 +16,10 @@ COPY ./pod/ . # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update && apt-get install -y netcat +RUN apt-get update \ + && apt-get install -y --no-install-recommends netcat\ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/dockerfile-dev-with-volumes/pod/Dockerfile b/dockerfile-dev-with-volumes/pod/Dockerfile index f459305a76..18197ab219 100755 --- a/dockerfile-dev-with-volumes/pod/Dockerfile +++ b/dockerfile-dev-with-volumes/pod/Dockerfile @@ -19,12 +19,15 @@ FROM $PYTHON_VERSION # TODO #FROM harbor.urba.univ-lille.fr/store/python:3.7-buster -RUN apt-get clean && apt-get update \ - && apt-get install -y netcat \ - ffmpeg \ - ffmpegthumbnailer \ - imagemagick \ - gettext +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + netcat \ + ffmpeg \ + ffmpegthumbnailer \ + imagemagick \ + gettext \ + && apt-get clean\ + && rm -rf /var/lib/apt/lists/* WORKDIR /usr/src/app diff --git a/pod/authentication/admin.py b/pod/authentication/admin.py index e22f86eb6e..80967ad8c3 100644 --- a/pod/authentication/admin.py +++ b/pod/authentication/admin.py @@ -119,7 +119,7 @@ def get_readonly_fields(self, request, obj=None): self.readonly_fields += ("is_superuser",) return self.readonly_fields - def owner_hashkey(self, obj): + def owner_hashkey(self, obj) -> str: return "%s" % Owner.objects.get(user=obj).hashkey def formfield_for_manytomany(self, db_field, request, **kwargs): @@ -130,7 +130,7 @@ def formfield_for_manytomany(self, db_field, request, **kwargs): kwargs["widget"] = widgets.FilteredSelectMultiple(db_field.verbose_name, False) return super().formfield_for_foreignkey(db_field, request, **kwargs) - def owner_establishment(self, obj): + def owner_establishment(self, obj) -> str: return "%s" % Owner.objects.get(user=obj).establishment owner_establishment.short_description = _("Establishment") @@ -146,7 +146,7 @@ def get_queryset(self, request): qs = qs.filter(owner__sites=get_current_site(request)) return qs - def save_model(self, request, obj, form, change): + def save_model(self, request, obj, form, change) -> None: super().save_model(request, obj, form, change) if not change: obj.owner.sites.add(get_current_site(request)) @@ -174,7 +174,7 @@ def get_queryset(self, request): qs = qs.filter(groupsite__sites=get_current_site(request)) return qs - def save_model(self, request, obj, form, change): + def save_model(self, request, obj, form, change) -> None: super().save_model(request, obj, form, change) if not change: obj.groupsite.sites.add(get_current_site(request)) diff --git a/pod/authentication/apps.py b/pod/authentication/apps.py index b753a5727a..ed200d7946 100644 --- a/pod/authentication/apps.py +++ b/pod/authentication/apps.py @@ -1,10 +1,11 @@ +"""Esup-Pod Authentication apps.""" from django.apps import AppConfig from django.db.models.signals import post_migrate from django.core.exceptions import ObjectDoesNotExist from django.utils.translation import gettext_lazy as _ -def create_groupsite_if_not_exists(g): +def create_groupsite_if_not_exists(g) -> None: from pod.authentication.models import GroupSite try: @@ -13,7 +14,7 @@ def create_groupsite_if_not_exists(g): GroupSite.objects.create(group=g) -def set_default_site(sender, **kwargs): +def set_default_site(sender, **kwargs) -> None: from pod.authentication.models import Owner from django.contrib.sites.models import Site from django.contrib.auth.models import Group @@ -22,11 +23,11 @@ def set_default_site(sender, **kwargs): for g in Group.objects.all(): create_groupsite_if_not_exists(g) for gs in GroupSite.objects.all(): - if len(gs.sites.all()) == 0: + if gs.sites.count() == 0: gs.sites.add(Site.objects.get_current()) gs.save() for owner in Owner.objects.all(): - if len(owner.sites.all()) == 0: + if owner.sites.count() == 0: owner.sites.add(Site.objects.get_current()) owner.save() @@ -36,5 +37,5 @@ class AuthConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" verbose_name = _("Authentication") - def ready(self): + def ready(self) -> None: post_migrate.connect(set_default_site, sender=self) diff --git a/pod/authentication/backends.py b/pod/authentication/backends.py index 3385bb704d..816916fd04 100644 --- a/pod/authentication/backends.py +++ b/pod/authentication/backends.py @@ -15,7 +15,7 @@ CREATE_GROUP_FROM_AFFILIATION = getattr(settings, "CREATE_GROUP_FROM_AFFILIATION", False) -def is_staff_affiliation(affiliation): +def is_staff_affiliation(affiliation) -> bool: """Check if user affiliation correspond to AFFILIATION_STAFF.""" return affiliation in AFFILIATION_STAFF @@ -48,7 +48,7 @@ def authenticate(self, request, remote_user, shib_meta): return user if self.user_can_authenticate(user) else None @staticmethod - def update_owner_params(user, params): + def update_owner_params(user, params) -> None: """Update owner params from Shibboleth.""" user.owner.auth_type = "Shibboleth" if get_current_site(None) not in user.owner.sites.all(): diff --git a/pod/authentication/context_processors.py b/pod/authentication/context_processors.py index 927e6fab44..c4402a1d09 100644 --- a/pod/authentication/context_processors.py +++ b/pod/authentication/context_processors.py @@ -1,9 +1,10 @@ +"""Esup-Pod authentication context_processors.""" from django.conf import settings as django_settings SHIB_NAME = getattr(django_settings, "SHIB_NAME", "Identify Federation") -def context_authentication_settings(request): +def context_authentication_settings(request) -> dict: new_settings = {} new_settings["SHIB_NAME"] = SHIB_NAME return new_settings diff --git a/pod/authentication/forms.py b/pod/authentication/forms.py index e920bae91d..11159d5a5b 100644 --- a/pod/authentication/forms.py +++ b/pod/authentication/forms.py @@ -15,7 +15,7 @@ class OwnerAdminForm(forms.ModelForm): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super(OwnerAdminForm, self).__init__(*args, **kwargs) if __FILEPICKER__: self.fields["userpicture"].widget = CustomFileWidget(type="image") @@ -26,7 +26,7 @@ class Meta(object): class GroupSiteAdminForm(forms.ModelForm): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super(GroupSiteAdminForm, self).__init__(*args, **kwargs) class Meta(object): @@ -52,7 +52,7 @@ class Meta(object): class SetNotificationForm(forms.ModelForm): """Push notification preferences form.""" - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super(SetNotificationForm, self).__init__(*args, **kwargs) class Meta(object): @@ -79,7 +79,7 @@ class Meta: fields = "__all__" exclude = [] - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: # Do the normal form initialisation. super(GroupAdminForm, self).__init__(*args, **kwargs) # If it is an existing group (saved objects have a pk). @@ -90,7 +90,7 @@ def __init__(self, *args, **kwargs): owner__sites=Site.objects.get_current() ) - def save_m2m(self): + def save_m2m(self) -> None: # Add the users to the Group. self.instance.user_set.set(self.cleaned_data["users"]) diff --git a/pod/authentication/models.py b/pod/authentication/models.py index 8db9f8be44..a09a6541ef 100644 --- a/pod/authentication/models.py +++ b/pod/authentication/models.py @@ -66,7 +66,7 @@ FILES_DIR = getattr(settings, "FILES_DIR", "files") -def get_name(self) -> str: +def get_name(self: User) -> str: """ Return the user's full name, including the username if not hidden. @@ -119,7 +119,7 @@ class Meta: verbose_name_plural = _("Owners") ordering = ["user"] - def __str__(self): + def __str__(self) -> str: if HIDE_USERNAME: return "%s %s" % (self.user.first_name, self.user.last_name) return "%s %s (%s)" % ( @@ -128,13 +128,13 @@ def __str__(self): self.user.username, ) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: self.hashkey = hashlib.sha256( (SECRET_KEY + self.user.username).encode("utf-8") ).hexdigest() super(Owner, self).save(*args, **kwargs) - def is_manager(self): + def is_manager(self) -> bool: group_ids = ( self.user.groups.all() .filter(groupsite__sites=Site.objects.get_current()) @@ -146,18 +146,18 @@ def is_manager(self): ) @property - def email(self): + def email(self) -> str: return self.user.email @receiver(post_save, sender=Owner) -def default_site_owner(sender, instance, created, **kwargs): - if len(instance.sites.all()) == 0: +def default_site_owner(sender, instance, created: bool, **kwargs) -> None: + if instance.sites.count() == 0: instance.sites.add(Site.objects.get_current()) @receiver(post_save, sender=User) -def create_owner_profile(sender, instance, created, **kwargs): +def create_owner_profile(sender, instance, created: bool, **kwargs) -> None: if created: try: Owner.objects.create(user=instance) @@ -179,13 +179,13 @@ class Meta: @receiver(post_save, sender=GroupSite) -def default_site_groupsite(sender, instance, created, **kwargs): - if len(instance.sites.all()) == 0: +def default_site_groupsite(sender, instance, created: bool, **kwargs) -> None: + if instance.sites.count() == 0: instance.sites.add(Site.objects.get_current()) @receiver(post_save, sender=Group) -def create_groupsite_profile(sender, instance, created, **kwargs): +def create_groupsite_profile(sender, instance, created: bool, **kwargs) -> None: if created: try: GroupSite.objects.create(group=instance) @@ -211,7 +211,7 @@ class AccessGroup(models.Model): through="Owner_accessgroups", ) - def __str__(self): + def __str__(self) -> str: return "%s" % (self.display_name) class Meta: diff --git a/pod/authentication/populatedCASbackend.py b/pod/authentication/populatedCASbackend.py index 76cca6ddee..d3aa4aff92 100644 --- a/pod/authentication/populatedCASbackend.py +++ b/pod/authentication/populatedCASbackend.py @@ -73,7 +73,7 @@ __SUBTREE__ = "SUBTREE" -def populateUser(tree): +def populateUser(tree) -> None: """Populate user form CAS or LDAP attributes.""" username_element = tree.find( ".//{http://www.yale.edu/tp/cas}%s" % AUTH_CAS_USER_SEARCH @@ -103,13 +103,13 @@ def populateUser(tree): populate_user_from_entry(user, owner, entry) -def delete_synchronized_access_group(owner): +def delete_synchronized_access_group(owner) -> None: groups_to_sync = AccessGroup.objects.filter(auto_sync=True) for group_to_sync in groups_to_sync: owner.accessgroup_set.remove(group_to_sync) -def get_server(): +def get_server() -> Server: if isinstance(LDAP_SERVER["url"], str): server = Server( LDAP_SERVER["url"], @@ -167,7 +167,7 @@ def get_entry(conn, username, list_value): return None -def assign_accessgroups(groups_element, user): +def assign_accessgroups(groups_element, user) -> None: for group in groups_element: if group in GROUP_STAFF: user.is_staff = True @@ -189,7 +189,7 @@ def assign_accessgroups(groups_element, user): pass -def create_accessgroups(user, tree_or_entry, auth_type): +def create_accessgroups(user, tree_or_entry, auth_type) -> None: groups_element = [] if auth_type == "cas": tree_groups_element = tree_or_entry.findall( @@ -229,7 +229,7 @@ def get_entry_value(entry, attribute, default): return default -def populate_user_from_entry(user, owner, entry): +def populate_user_from_entry(user, owner, entry) -> None: """Populate user and owner objects from the LDAP entry.""" if DEBUG: print(entry) @@ -260,7 +260,7 @@ def populate_user_from_entry(user, owner, entry): owner.save() -def populate_user_from_tree(user, owner, tree): +def populate_user_from_tree(user, owner, tree) -> None: """Populate user from CAS attributes.""" if DEBUG: print_xml_tree(tree) @@ -312,13 +312,13 @@ def populate_user_from_tree(user, owner, tree): owner.save() -def print_xml_tree(tree): +def print_xml_tree(tree) -> None: """Print XML tree for debug purpose.""" import xml.etree.ElementTree as ET - import xml.dom.minidom + from defusedxml import minidom import os - xml_string = xml.dom.minidom.parseString(ET.tostring(tree)).toprettyxml() + xml_string = minidom.parseString(ET.tostring(tree)).toprettyxml() xml_string = os.linesep.join( [s for s in xml_string.splitlines() if s.strip()] ) # remove the weird newline issue diff --git a/pod/authentication/tests/test_populated.py b/pod/authentication/tests/test_populated.py index 8939026a60..6fc9606f3a 100644 --- a/pod/authentication/tests/test_populated.py +++ b/pod/authentication/tests/test_populated.py @@ -107,7 +107,7 @@ class PopulatedCASTestCase(TestCase): """ - def setUp(self): + def setUp(self) -> None: """Set up PopulatedCASTestCase create user Pod.""" User.objects.create(username="pod", password="pod1234pod") AccessGroup.objects.create(code_name="groupTest", display_name="Group de test") @@ -117,7 +117,7 @@ def setUp(self): print(" ---> SetUp of PopulatedCASTestCase: OK!") @override_settings(DEBUG=False) - def test_populate_user_from_tree(self): + def test_populate_user_from_tree(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -141,7 +141,7 @@ def test_populate_user_from_tree(self): ) @override_settings(DEBUG=False, CREATE_GROUP_FROM_AFFILIATION=True) - def test_populate_user_from_tree_affiliation(self): + def test_populate_user_from_tree_affiliation(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -162,7 +162,7 @@ def test_populate_user_from_tree_affiliation(self): CREATE_GROUP_FROM_AFFILIATION=True, CREATE_GROUP_FROM_GROUPS=True, ) - def test_populate_user_from_tree_affiliation_group(self): + def test_populate_user_from_tree_affiliation_group(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -191,7 +191,7 @@ def test_populate_user_from_tree_affiliation_group(self): CREATE_GROUP_FROM_GROUPS=True, USER_CAS_MAPPING_ATTRIBUTES=USER_CAS_MAPPING_ATTRIBUTES_TEST_NOGROUPS, ) - def test_populate_user_from_tree_affiliation_nogroup(self): + def test_populate_user_from_tree_affiliation_nogroup(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -220,7 +220,7 @@ def test_populate_user_from_tree_affiliation_nogroup(self): CREATE_GROUP_FROM_GROUPS=True, POPULATE_USER="CAS", ) - def test_populate_user_from_tree_unpopulate_group(self): + def test_populate_user_from_tree_unpopulate_group(self) -> None: user = User.objects.get(username="pod") user.owner.accessgroup_set.add(AccessGroup.objects.get(code_name="groupTest")) user.owner.accessgroup_set.add(AccessGroup.objects.get(code_name="groupTest2")) @@ -294,7 +294,7 @@ class PopulatedLDAPTestCase(TestCase): } entry = "" - def setUp(self): + def setUp(self) -> None: """Set up PopulatedLDAPTestCase create user Pod.""" User.objects.create(username="pod", password="pod1234pod") AccessGroup.objects.create(code_name="groupTest", display_name="Group de test") @@ -321,7 +321,7 @@ def setUp(self): print(" ---> SetUp of PopulatedLDAPTestCase: OK!") @override_settings(DEBUG=False) - def test_populate_user_from_entry(self): + def test_populate_user_from_entry(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -344,7 +344,7 @@ def test_populate_user_from_entry(self): ) @override_settings(DEBUG=False, CREATE_GROUP_FROM_AFFILIATION=True) - def test_populate_user_from_entry_affiliation(self): + def test_populate_user_from_entry_affiliation(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -364,7 +364,7 @@ def test_populate_user_from_entry_affiliation(self): CREATE_GROUP_FROM_AFFILIATION=True, CREATE_GROUP_FROM_GROUPS=True, ) - def test_populate_user_from_entry_affiliation_group(self): + def test_populate_user_from_entry_affiliation_group(self) -> None: owner = Owner.objects.get(user__username="pod") user = User.objects.get(username="pod") self.assertEqual(user.owner, owner) @@ -393,7 +393,7 @@ def test_populate_user_from_entry_affiliation_group(self): CREATE_GROUP_FROM_GROUPS=True, POPULATE_USER="LDAP", ) - def test_populate_user_from_entry_unpopulate_group(self): + def test_populate_user_from_entry_unpopulate_group(self) -> None: user = User.objects.get(username="pod") user.owner.accessgroup_set.add(AccessGroup.objects.get(code_name="groupTest")) user.owner.accessgroup_set.add( @@ -432,7 +432,7 @@ def test_populate_user_from_entry_unpopulate_group(self): class PopulatedShibTestCase(TestCase): - def setUp(self): + def setUp(self) -> None: """Set up PopulatedShibTestCase create user Pod.""" self.hmap = {} for a in SHIBBOLETH_ATTRIBUTE_MAP: @@ -456,13 +456,13 @@ def _authenticate_shib_user(self, u): fake_shib_header[self.hmap["affiliation"]] = u["affiliations"].split(";")[0] fake_shib_header[self.hmap["affiliations"]] = u["affiliations"] - """Get valid shib_meta from simulated shibboleth header """ + # Get valid shib_meta from simulated shibboleth header. request = RequestFactory().get("/", REMOTE_USER=u["username"]) request.META.update(**fake_shib_header) shib_meta, error = shibmiddleware.ShibbMiddleware.parse_attributes(request) self.assertFalse(error, "Generating shibboleth attribute mapping contains errors") - """Check user authentication """ + # Check user authentication. user = ShibbBackend.authenticate( ShibbBackend(), request=request, @@ -474,7 +474,7 @@ def _authenticate_shib_user(self, u): return (user, shib_meta) @override_settings(DEBUG=False) - def test_make_profile(self): + def test_make_profile(self) -> None: """Test if user attributes are retrieved.""" user, shib_meta = self._authenticate_shib_user( { @@ -490,7 +490,7 @@ def test_make_profile(self): self.assertEqual(user.first_name, "John") self.assertEqual(user.last_name, "Do") - """Test if user can be staff if SHIBBOLETH_STAFF_ALLOWED_DOMAINS is None """ + # Test if user can be staff if SHIBBOLETH_STAFF_ALLOWED_DOMAINS is None. settings.SHIBBOLETH_STAFF_ALLOWED_DOMAINS = None reload(shibmiddleware) shibmiddleware.ShibbMiddleware.make_profile( @@ -524,7 +524,7 @@ def test_make_profile(self): ) self.assertTrue(user.is_staff) - """Test if same user with new unstaffable affiliation keep his staff status """ + # Test if same user with new unstaffable affiliation keep his staff status. for a in UNSTAFFABLE_AFFILIATIONS: self.assertFalse(a in AFFILIATION_STAFF) user, shib_meta = self._authenticate_shib_user( @@ -546,7 +546,7 @@ def test_make_profile(self): owner = Owner.objects.get(user__username="jdo@univ.fr") self.assertEqual(owner.affiliation, "member") - """Test if a new user with same unstaffable affiliations has no staff status""" + # Test if a new user with same unstaffable affiliations has no staff status. user, shib_meta = self._authenticate_shib_user( { "username": "ada@univ.fr", diff --git a/pod/authentication/tests/test_views.py b/pod/authentication/tests/test_views.py index 2efe8071d7..7ead289664 100644 --- a/pod/authentication/tests/test_views.py +++ b/pod/authentication/tests/test_views.py @@ -34,7 +34,7 @@ def test_authentication_login_gateway(self): of authenticationViewsTestCase: OK!" ) - def test_authentication_login(self): + def test_authentication_login(self) -> None: """Test authentication login page.""" self.client = Client() self.user = User.objects.get(username="pod") @@ -55,7 +55,7 @@ def test_authentication_login(self): of authenticationViewsTestCase: OK!" ) - def test_authentication_logout(self): + def test_authentication_logout(self) -> None: self.client = Client() # USE_CAS is valued to False response = self.client.get("/authentication_logout/") diff --git a/pod/bbb/bbb.py b/pod/bbb/bbb.py index f34283f5af..b218b2b81a 100644 --- a/pod/bbb/bbb.py +++ b/pod/bbb/bbb.py @@ -33,7 +33,7 @@ log = logging.getLogger(__name__) -def start_bbb_encode(id): +def start_bbb_encode(id) -> None: if CELERY_TO_ENCODE: task_start_bbb_encode.delay(id) else: @@ -43,7 +43,7 @@ def start_bbb_encode(id): t.start() -def bbb_encode_meeting(id): +def bbb_encode_meeting(id) -> None: msg = "" # Get the meeting diff --git a/pod/bbb/forms.py b/pod/bbb/forms.py index 69824b7b4e..0a727a7521 100644 --- a/pod/bbb/forms.py +++ b/pod/bbb/forms.py @@ -16,7 +16,7 @@ class MeetingForm(forms.ModelForm): - def __init__(self, request, *args, **kwargs): + def __init__(self, request, *args, **kwargs) -> None: super(MeetingForm, self).__init__(*args, **kwargs) # All fields are hidden. This form is like a Confirm prompt @@ -43,7 +43,7 @@ class Meta: @receiver(post_save, sender=BBB_Meeting) -def launch_encode(sender, instance, created, **kwargs): +def launch_encode(sender, instance, created, **kwargs) -> None: # Useful when an administrator send a re-encode task if hasattr(instance, "launch_encode") and instance.launch_encode is True: instance.launch_encode = False @@ -62,7 +62,7 @@ def launch_encode(sender, instance, created, **kwargs): class LivestreamForm(forms.ModelForm): - def __init__(self, request, *args, **kwargs): + def __init__(self, request, *args, **kwargs) -> None: super(LivestreamForm, self).__init__(*args, **kwargs) # All fields are hidden. This form is like a Confirm prompt diff --git a/pod/bbb/models.py b/pod/bbb/models.py index aafd4e5d2a..bbe60ffdfc 100644 --- a/pod/bbb/models.py +++ b/pod/bbb/models.py @@ -1,3 +1,4 @@ +"""Esup-Pod BBB models.""" import importlib from django.db import models @@ -85,13 +86,13 @@ class BBB_Meeting(models.Model): help_text=_("Last date where BBB session was in progress."), ) - def __unicode__(self): + def __unicode__(self) -> str: return "%s - %s" % (self.meeting_name, self.meeting_id) - def __str__(self): + def __str__(self) -> str: return "%s - %s" % (self.meeting_name, self.meeting_id) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: super(BBB_Meeting, self).save(*args, **kwargs) class Meta: @@ -101,7 +102,7 @@ class Meta: @receiver(post_save, sender=BBB_Meeting) -def process_recording(sender, instance, created, **kwargs): +def process_recording(sender, instance, created, **kwargs) -> None: # Convert the BBB presentation only one time # Be careful: this is the condition to create a video of the # BigBlueButton presentation only one time. @@ -148,13 +149,13 @@ class Attendee(models.Model): help_text=_("User from the Pod database, if user found."), ) - def __unicode__(self): + def __unicode__(self) -> str: return "%s - %s" % (self.full_name, self.role) - def __str__(self): + def __str__(self) -> str: return "%s - %s" % (self.full_name, self.role) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: super(Attendee, self).save(*args, **kwargs) class Meta: @@ -270,13 +271,13 @@ class Livestream(models.Model): help_text=_("Redis channel, useful for chat"), ) - def __unicode__(self): + def __unicode__(self) -> str: return "%s - %s" % (self.meeting, self.status) - def __str__(self): + def __str__(self) -> str: return "%s - %s" % (self.meeting, self.status) - def save(self, *args, **kwargs): + def save(self, *args, **kwargs) -> None: super(Livestream, self).save(*args, **kwargs) class Meta: diff --git a/pod/chapter/static/js/chapters.js b/pod/chapter/static/js/chapters.js index 5cb8d76d88..f0dee55b35 100644 --- a/pod/chapter/static/js/chapters.js +++ b/pod/chapter/static/js/chapters.js @@ -12,10 +12,20 @@ global player */ +// Read-only globals defined in main.js +/* +global fadeIn +*/ + var id_form = "form_chapter"; + +/** + * Display the chapter form + * @param {*} data + */ function show_form(data) { let form_chapter = document.getElementById(id_form); - form_chapter.style.display = "none"; + form_chapter.classList.add("d-none"); form_chapter.innerHTML = data; form_chapter.querySelectorAll("script").forEach((item) => { // run script tags of filewidget.js and custom_filewidget.js @@ -66,19 +76,6 @@ function show_form(data) { } } -var showalert = function (message, alerttype) { - document.body.append( - '', - ); - setTimeout(function () { - document.getElementById("formalertdiv").remove(); - }, 5000); -}; - var ajaxfail = function (data) { showalert( gettext("Error getting form.") + @@ -134,7 +131,7 @@ var sendandgetform = async function (elt) { }); const data = await response.text(); if ( - data.indexOf(id_form) == -1 && + data.indexOf(id_form) === -1 && (action === "new" || action === "modify") ) { showalert( @@ -150,7 +147,7 @@ var sendandgetform = async function (elt) { show_form(data); elt.classList.add("info"); } else if (action === "delete") { - if (data.indexOf("list_chapter") == -1) { + if (data.indexOf("list_chapter") === -1) { showalert( gettext("You are no longer authenticated. Please log in again."), "alert-danger", @@ -175,14 +172,14 @@ var sendform = async function (elt, action) { "X-Requested-With": "XMLHttpRequest", }; const url = window.location.href; - let form_save; let data_form; let validationMessage; + let form_chapter = document.getElementById(id_form); + let form_save = form_chapter.querySelector("form"); if (action === "save") { if (verify_start_title_items()) { - form_save = document.getElementById("form_chapter").querySelector("form"); - form_save.style.display = "none"; + form_chapter.classList.add("d-none"); data_form = new FormData(form_save); validationMessage = gettext( "Make sure your chapter start time is not 0 or equal to another chapter start time.", @@ -212,7 +209,7 @@ var sendform = async function (elt, action) { dataType: "html", }); const data = await response.text(); - if (data.indexOf("list_chapter") == -1 && data.indexOf("form") == -1) { + if (data.indexOf("list_chapter") === -1 && data.indexOf("form") === -1) { showalert( gettext("You are no longer authenticated. Please log in again."), "alert-danger", @@ -220,7 +217,7 @@ var sendform = async function (elt, action) { } else { const jsonData = JSON.parse(data); if (jsonData.errors) { - document.getElementById("form_chapter").style.display = "block"; + form_chapter.classList.remove("d-none"); showalert(jsonData.errors + " " + validationMessage, "alert-danger"); } else { updateDom(jsonData); @@ -257,10 +254,10 @@ function verify_start_title_items() { inputTitle.value.length > 100 ) { if (typeof lengthErrorMsg === "undefined") { - let lengthErrorMsg = document.createElement("div"); + var lengthErrorMsg = document.createElement("div"); lengthErrorMsg.id = errormsg_id; lengthErrorMsg.className = "invalid-feedback"; - lengthErrorMsg.innerHTML = gettext( + lengthErrorMsg.textContent = gettext( "Please enter a title from 2 to 100 characters.", ); inputTitle.insertAdjacentHTML("afterend", lengthErrorMsg.outerHTML); @@ -298,10 +295,10 @@ function verify_start_title_items() { inputStart.value >= video_duration ) { if (typeof timeErrorMsg === "undefined") { - let timeErrorMsg = document.createElement("div"); + var timeErrorMsg = document.createElement("div"); timeErrorMsg.id = errormsg_id; timeErrorMsg.className = "invalid-feedback"; - timeErrorMsg.innerHTML = + timeErrorMsg.textContent = gettext("Please enter a correct start field between 0 and") + " " + (video_duration - 1); @@ -329,8 +326,8 @@ function verify_start_title_items() { /** Unused function. TODO: delete in 3.7.0 function overlaptest() { - var new_start = parseInt(document.getElementById("id_time_start").value); - var id = parseInt(document.getElementById("id_chapter").value); + var new_start = parseInt(document.getElementById("id_time_start").value, 10); + var id = parseInt(document.getElementById("id_chapter").value, 10); var msg = ""; document.querySelectorAll("ul#chapters li").foreach(function (li) { if ( @@ -378,7 +375,7 @@ const updateTimeCode = (event) => { } const parent = event.target.parentNode; parent.querySelectorAll("span.getfromvideo span.timecode").forEach((span) => { - span.innerHTML = " " + parseInt(event.target.value).toHHMMSS(); + span.textContent = " " + parseInt(event.target.value, 10).toHHMMSS(); }); }; diff --git a/pod/chapter/static/js/videojs-chapters.js b/pod/chapter/static/js/videojs-chapters.js index b87710bd5a..8807a9033e 100644 --- a/pod/chapter/static/js/videojs-chapters.js +++ b/pod/chapter/static/js/videojs-chapters.js @@ -74,7 +74,7 @@ this.chapters = groupedChapters(data); var ol = document.getElementById("chapters-list"); - for (var i = 0; i < this.chapters.id.length; ++i) { + for (let i = 0; i < this.chapters.id.length; ++i) { var chapId = this.chapters.id[i]; var chapTitle = this.chapters.title[i]; var chapTime = this.chapters.start[i]; @@ -119,10 +119,10 @@ title: [], start: [], }; - for (var i = 0; i < data.length; i++) { - chapters.id.push(parseInt(data[i].attributes[1].value)); + for (let i = 0; i < data.length; i++) { + chapters.id.push(parseInt(data[i].attributes[1].value, 10)); chapters.title.push(data[i].attributes[2].value); - chapters.start.push(parseInt(data[i].attributes[0].value)); + chapters.start.push(parseInt(data[i].attributes[0].value, 10)); } return chapters; } diff --git a/pod/completion/static/js/caption_maker.js b/pod/completion/static/js/caption_maker.js index f38e7b97b8..561428e27c 100644 --- a/pod/completion/static/js/caption_maker.js +++ b/pod/completion/static/js/caption_maker.js @@ -7,6 +7,13 @@ global current_folder */ +// Read-only globals defined in video-script.html +/* +global player +*/ + +/* exported processProxyVttResponse */ + // Global vars var fileLoaded = false; var fileLoadedId = undefined; @@ -117,14 +124,14 @@ document.addEventListener("click", (evt) => { saveModal.hide(); let form_save_captions = document.getElementById("form_save_captions"); - if (evt.target.id == "modal-btn-override") { + if (evt.target.id === "modal-btn-override") { document .getElementById("form_save_captions") .querySelector('input[name="file_id"]').value = fileLoadedId; //form_save_captions.querySelector('input[name="enrich_ready"]').value = ""; updateCaptionsArray(caption_content.value); send_form_save_captions(); - } else if (evt.target.id == "modal-btn-new") { + } else if (evt.target.id === "modal-btn-new") { form_save_captions.querySelector('input[name="file_id"]').value = ""; //form_save_captions.querySelector('input[name="enrich_ready"]').value=""; @@ -137,7 +144,7 @@ document.addEventListener("click", (evt) => { */ const send_form_save_captions = function () { let fileName = document.getElementById("captionFilename").value; - if (fileName.length == 0) { + if (fileName.length === 0) { fileName = `${file_prefix}_captions_${Date.now()}`; } @@ -250,7 +257,7 @@ document.getElementById("addSubtitle").addEventListener("click", function () { var captionsEndTime = existingCaptionsEndTime(); addCaption( captionsEndTime, - playTime > captionsEndTime ? playTime : parseInt(captionsEndTime) + 2, + playTime > captionsEndTime ? playTime : parseInt(captionsEndTime, 10) + 2, "", ); }); @@ -264,7 +271,7 @@ document autoPauseAtTime = -1; document.getElementById("captionContent").value = ""; - document.getElementById("captionTitle").innerHTML = " "; + document.getElementById("captionTitle").textContent = " "; document.getElementById("textCaptionEntry").value = ""; document.querySelectorAll(".newEditorBlock").forEach((e) => { e.remove(); @@ -315,7 +322,7 @@ function displayExistingCaption(seconds) { document.getElementById("textCaptionEntry").value = theCaption.caption; //document.getElementById("previewTrack").value = theCaption.caption; } else { - document.getElementById("captionTitle").innerHTML = " "; + document.getElementById("captionTitle").textContent = " "; document.getElementById("textCaptionEntry").value = ""; //document.getElementById("previewTrack").value = ""; } @@ -454,8 +461,7 @@ function videoTimeUpdateEventHandler() { ]); let divs = document.querySelectorAll(".vjs-text-track-display div"); - divs[divs.length - 1].innerText = ""; - + divs[divs.length - 1].innertext = ""; if (captionBeingDisplayed != -1) { document.getElementById("textCaptionEntry").value = ""; captionBeingDisplayed = -1; @@ -609,7 +615,7 @@ document .getElementById("textCaptionEntry") .addEventListener("keydown", function (e) { var code = e.key ?? e.code; - if (code == "ENTER" && !e.shiftKey) { + if (code === "ENTER" && !e.shiftKey) { document.getElementById("saveCaptionAndPlay").click(); return false; } @@ -836,7 +842,7 @@ function createCaptionBlock(newCaption, spawnFunction) { let captionObj = { start: newCaption.end, end: - playTime > newCaption.end ? playTime : parseInt(newCaption.end) + 2, + playTime > newCaption.end ? playTime : parseInt(newCaption.end, 10) + 2, caption: "", }; let index = Array.from(this.div.parentNode.children).indexOf(this.div); @@ -905,11 +911,11 @@ function createCaptionBlock(newCaption, spawnFunction) { ); this.startTimeInput.addEventListener("keydown", (e) => { - if (e.key == "ENTER") this.disableEdit(); + if (e.key === "ENTER") this.disableEdit(); }); this.endTimeInput.addEventListener("keydown", (e) => { - if (e.key == "ENTER") this.disableEdit(); + if (e.key === "ENTER") this.disableEdit(); }); document.addEventListener("click", (e) => { @@ -1050,7 +1056,7 @@ let editorShortcuts = { */ notFocused: function () { var focused = document.activeElement; - return focused.length == 0; + return focused.length === 0; }, init: function () { @@ -1162,7 +1168,7 @@ function formatTime(seconds) { var mm = Math.floor(seconds / 60) % 60; var ss = seconds % 60; return ( - (hh == 0 ? "" : (hh < 10 ? "0" : "") + hh.toString() + ":") + + (hh === 0 ? "" : (hh < 10 ? "0" : "") + hh.toString() + ":") + (mm < 10 ? "0" : "") + mm.toString() + ":" + @@ -1275,9 +1281,9 @@ function loadCaptionFile(fileObject) { */ function processProxyVttResponse(obj) { obj = JSON.parse(obj); - if (obj.status == "error") + if (obj.status === "error") alert(gettext("Error loading caption file: ") + obj.message); - else if (obj.status == "success") { + else if (obj.status === "success") { // delete any captions we've got captionsArray.length = 0; fileLoaded = true; @@ -1343,13 +1349,13 @@ function parseAndLoadWebVTT(vtt) { cueStart = cueEnd = cueText = null; } - for (var i = 1; i < vttLines.length; i++) { + for (let i = 1; i < vttLines.length; i++) { if (rxBlankLine.test(vttLines[i])) { appendCurrentCaption(); continue; } - if (!cueStart && !cueEnd && !cueText && vttLines[i].indexOf("-->") == -1) { + if (!cueStart && !cueEnd && !cueText && vttLines[i].indexOf("-->") === -1) { // this is a cue identifier we're ignoring continue; } @@ -1358,7 +1364,7 @@ function parseAndLoadWebVTT(vtt) { if (timeMatch) { appendCurrentCaption(); cueStart = parseTime(timeMatch[1]); - if (cueStart == 0) cueStart = "0.0"; + if (cueStart === 0) cueStart = "0.0"; cueEnd = parseTime(timeMatch[2]); continue; } @@ -1443,25 +1449,26 @@ const onPlayerReady = function (player, options) { regionHighlight.after(endKeyframe); }; - /** - * Seek video player to absolute `time`. - * @param {[type]} time [description] - */ - seekVideoTo = function (time) { - player.userActive(true); - player.currentTime(time); - }; - - /** - * Seek video player to relative `time`. - * @param {[type]} time [description] - */ - seekVideo = function (time) { - player.userActive(true); - player.currentTime(player.currentTime() + time); - }; }; +/** + * Seek video player to absolute `time`. + * @param {[type]} time [description] + */ +function seekVideoTo(time) { + player.userActive(true); + player.currentTime(time); +} + +/** + * Seek video player to relative `time`. + * @param {[type]} time [description] + */ +function seekVideo(time) { + player.userActive(true); + player.currentTime(player.currentTime() + time); +} + /** * Timeline regions * @param {[type]} options [description] diff --git a/pod/completion/static/js/completion.js b/pod/completion/static/js/completion.js index 46b99b0e7b..a3fbd3936d 100644 --- a/pod/completion/static/js/completion.js +++ b/pod/completion/static/js/completion.js @@ -1,3 +1,12 @@ +/** + * @file Esup-Pod Completion scripts. + */ + +// Read-only globals defined in main.js +/* +global fadeIn +*/ + document.addEventListener("DOMContentLoaded", function () { document.querySelectorAll("li.contenuTitre").forEach(function (element) { element.style.display = "none"; @@ -112,6 +121,10 @@ document.addEventListener("submit", (e) => { */ var sendAndGetForm = async function (elt, action, name, form, list) { var href = elt.getAttribute("action"); + let url = window.location.origin + href; + let token = elt.csrfmiddlewaretoken.value; + var id = elt.querySelector("input[name=id]").value; + if (action === "new" || action === "form_save_new") { document.getElementById(form).innerHTML = '
'; @@ -134,9 +147,7 @@ var sendAndGetForm = async function (elt, action, name, form, list) { // do nothing } - let url = window.location.origin + href; - let token = elt.csrfmiddlewaretoken.value; - form_data = new FormData(elt); + let form_data = new FormData(elt); await fetch(url, { method: "POST", @@ -151,7 +162,7 @@ var sendAndGetForm = async function (elt, action, name, form, list) { .then((response) => response.text()) .then((data) => { //parse data into html and log it - if (data.indexOf(form) == -1) { + if (data.indexOf(form) === -1) { showalert( gettext( "You are no longer authenticated. Please log in again.", @@ -168,7 +179,7 @@ var sendAndGetForm = async function (elt, action, name, form, list) { document.querySelector("form.form_modif").style.display = "block"; document.querySelector("form.form_delete").style.display = "block"; document.querySelector("form.form_new").style.display = "block"; - document.getElementById(form).innerHTML = ""; + document.getElementById(form).textContent = ""; }); const formClasses = [ @@ -189,10 +200,7 @@ var sendAndGetForm = async function (elt, action, name, form, list) { hide_others_sections(name); } if (action == "modify" || action == "form_save_modify") { - var id = elt.querySelector("input[name=id]").value; - var url = window.location.origin + href; - var token = document.csrfmiddlewaretoken.value; - form_data = new FormData(); + let form_data = new FormData(); form_data.append("action", action); form_data.append("id", id); @@ -206,7 +214,7 @@ var sendAndGetForm = async function (elt, action, name, form, list) { }) .then((response) => response.text()) .then((data) => { - if (data.indexOf(form) == -1) { + if (data.indexOf(form) === -1) { showalert( gettext( "You are no longer authenticated. Please log in again.", @@ -222,7 +230,7 @@ var sendAndGetForm = async function (elt, action, name, form, list) { document.querySelector("form.form_modif").style.display = "block"; document.querySelector("form.form_delete").style.display = "block"; document.querySelector("form.form_new").style.display = "block"; - document.getElementById(form).innerHTML = ""; + document.getElementById(form).textContent = ""; }); document.querySelector("a.title").style.display = "none"; @@ -248,9 +256,9 @@ var sendAndGetForm = async function (elt, action, name, form, list) { ); } if (deleteConfirm) { - var id = elt.querySelector("input[name=id]").value; - var url = window.location.origin + href; - var token = document.querySelector( + id = elt.querySelector("input[name=id]").value; + url = window.location.origin + href; + token = document.querySelector( "input[name=csrfmiddlewaretoken]", ).value; let form_data = new FormData(); @@ -351,8 +359,7 @@ function hide_others_sections(name_form) { slideUp(element.parentNode.nextElementSibling); element.classList.remove("active"); }); - var i; - for (i = 0; i < sections.length; i++) { + for (let i = 0; i < sections.length; i++) { var section = sections[i]; var text = section.text; var name_section = "'" + text.replace(/\s/g, "") + "'"; @@ -425,7 +432,7 @@ function verify_fields(form) { form_group.classList.add("has-error"); error = true; } - var id = parseInt(document.getElementById("id_contributor").value); + var id = parseInt(document.getElementById("id_contributor").value, 10); var new_role = document.getElementById("id_role").value; var new_name = document.getElementById("id_name").value; document @@ -463,7 +470,7 @@ function verify_fields(form) { error = true; } - var element = document.getElementById("id_lang"); + element = document.getElementById("id_lang"); var lang = element.options[element.selectedIndex].value .trim() .toLowerCase(); @@ -507,7 +514,7 @@ function verify_fields(form) { error = true; } var is_duplicate = false; - var file_name = file_abs_path.match(/([\w\d_\-]+)(\.vtt)/)[1].toLowerCase(); + var file_name = file_abs_path.match(/([\w\d_-]+)(\.vtt)/)[1].toLowerCase(); document .querySelectorAll(".grid-list-track .track_kind.kind") .forEach((elt) => { diff --git a/pod/custom/tenants/create_enc_tenant.sh b/pod/custom/tenants/create_enc_tenant.sh index dc13ec62fb..29c8a54210 100644 --- a/pod/custom/tenants/create_enc_tenant.sh +++ b/pod/custom/tenants/create_enc_tenant.sh @@ -9,34 +9,34 @@ echo "Script executed from: ${PWD}" BASEDIR=${PWD} echo "remove previous site" -rm -rf ./$NAME -sudo rm -f /etc/init.d/celeryd_$NAME -sudo rm -f /etc/default/celeryd_$NAME +rm -rf "./$NAME" +sudo rm -f "/etc/init.d/celeryd_$NAME" +sudo rm -f "/etc/default/celeryd_$NAME" echo "Creation du site numéro $ID_SITE ayant pour identifiant $NAME et pour domaine $DOMAIN_NAME" echo "Creation du répertoire" -cp -r ./source_enc ./$NAME +cp -r ./source_enc "./$NAME" echo "Fichier de configuration" -mv "./$NAME/tenant_enc_settings.py" "./$NAME/"$NAME"_enc_settings.py" +mv "./$NAME/tenant_enc_settings.py" ./$NAME/"$NAME"_enc_settings.py sed -i "s/__NAME__/$NAME/g" ./$NAME/"$NAME"_enc_settings.py sed -i "s/__ID_SITE__/$ID_SITE/g" ./$NAME/"$NAME"_enc_settings.py sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" ./$NAME/"$NAME"_enc_settings.py echo "Fichier de lancement Celery" -sudo cp /etc/init.d/celeryd /etc/init.d/celeryd_$NAME +sudo cp /etc/init.d/celeryd "/etc/init.d/celeryd_$NAME" mv "./$NAME/celeryd-tenant" "./$NAME/celeryd_$NAME" -sed -i "s/__NAME__/$NAME/g" ./$NAME/celeryd_$NAME -sed -i "s/__ID_SITE__/$ID_SITE/g" ./$NAME/celeryd_$NAME -sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" ./$NAME/celeryd_$NAME +sed -i "s/__NAME__/$NAME/g" "./$NAME/celeryd_$NAME" +sed -i "s/__ID_SITE__/$ID_SITE/g" "./$NAME/celeryd_$NAME" +sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" "./$NAME/celeryd_$NAME" #sudo ln -s $BASEDIR/$NAME/celeryd_$NAME /etc/default/celeryd_$NAME -sudo cp $BASEDIR/$NAME/celeryd_$NAME /etc/default/celeryd_$NAME -sudo chmod 640 /etc/default/celeryd_$NAME +sudo cp "$BASEDIR/$NAME/celeryd_$NAME" "/etc/default/celeryd_$NAME" +sudo chmod 640 "/etc/default/celeryd_$NAME" echo "fichier de configuration Celery" -sed -i "s/__NAME__/$NAME/g" ./$NAME/celery.py -sed -i "s/__ID_SITE__/$ID_SITE/g" ./$NAME/celery.py -sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" ./$NAME/celery.py +sed -i "s/__NAME__/$NAME/g" "./$NAME/celery.py" +sed -i "s/__ID_SITE__/$ID_SITE/g" "./$NAME/celery.py" +sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" "./$NAME/celery.py" echo "__FIN__" echo "Pour lancer le woker, il suffit de lancer cette commande :" diff --git a/pod/custom/tenants/create_tenant.sh b/pod/custom/tenants/create_tenant.sh index ff00ff3a62..338d0991d6 100644 --- a/pod/custom/tenants/create_tenant.sh +++ b/pod/custom/tenants/create_tenant.sh @@ -9,37 +9,37 @@ echo "Script executed from: ${PWD}" BASEDIR=${PWD} echo "remove previous site" -rm -rf ./$NAME +rm -rf "./$NAME" sudo systemctl stop uwsgi-pod_$NAME -sudo rm -f /etc/systemd/system/uwsgi-pod_$NAME.service -sudo rm -f /etc/nginx/sites-enabled/pod_nginx_$NAME.conf +sudo rm -f "/etc/systemd/system/uwsgi-pod_$NAME.service" +sudo rm -f "/etc/nginx/sites-enabled/pod_nginx_$NAME.conf" sudo rabbitmqctl delete_vhost rabbitpod-$NAME echo "Creation du site numéro $ID_SITE ayant pour identifiant $NAME et pour domaine $DOMAIN_NAME" echo "Creation du répertoire" -cp -r ./source ./$NAME +cp -r ./source "./$NAME" echo "Fichier de configuration" -mv "./$NAME/tenant_settings.py" "./$NAME/"$NAME"_settings.py" +mv "./$NAME/tenant_settings.py" ./$NAME/"$NAME"_settings.py sed -i "s/__NAME__/$NAME/g" ./$NAME/"$NAME"_settings.py sed -i "s/__ID_SITE__/$ID_SITE/g" ./$NAME/"$NAME"_settings.py sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" ./$NAME/"$NAME"_settings.py echo "Fichier vhost nginx" -mv ./$NAME/pod_nginx_tenant.conf ./$NAME/pod_nginx_$NAME.conf -sed -i "s/__NAME__/$NAME/g" ./$NAME/pod_nginx_$NAME.conf -sed -i "s/__ID_SITE__/$ID_SITE/g" ./$NAME/pod_nginx_$NAME.conf -sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" ./$NAME/pod_nginx_$NAME.conf +mv "./$NAME/pod_nginx_tenant.conf" "./$NAME/pod_nginx_$NAME.conf" +sed -i "s/__NAME__/$NAME/g" "./$NAME/pod_nginx_$NAME.conf" +sed -i "s/__ID_SITE__/$ID_SITE/g" "./$NAME/pod_nginx_$NAME.conf" +sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" "./$NAME/pod_nginx_$NAME.conf" echo "Fichier ini" -mv ./$NAME/pod_uwsgi_tenant.ini ./$NAME/pod_uwsgi_$NAME.ini -sed -i "s/__NAME__/$NAME/g" ./$NAME/pod_uwsgi_$NAME.ini -sed -i "s/__ID_SITE__/$ID_SITE/g" ./$NAME/pod_uwsgi_$NAME.ini -sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" ./$NAME/pod_uwsgi_$NAME.ini +mv "./$NAME/pod_uwsgi_tenant.ini" "./$NAME/pod_uwsgi_$NAME.ini" +sed -i "s/__NAME__/$NAME/g" "./$NAME/pod_uwsgi_$NAME.ini" +sed -i "s/__ID_SITE__/$ID_SITE/g" "./$NAME/pod_uwsgi_$NAME.ini" +sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" "./$NAME/pod_uwsgi_$NAME.ini" echo "Activation du vhost" -sudo ln -s $BASEDIR/$NAME/pod_nginx_$NAME.conf /etc/nginx/sites-enabled/pod_nginx_$NAME.conf +sudo ln -s "$BASEDIR/$NAME/pod_nginx_$NAME.conf" "/etc/nginx/sites-enabled/pod_nginx_$NAME.conf" ## sudo /etc/init.d/nginx restart echo "****************" echo "--> add /home/pod/ssl/$NAME/$DOMAIN_NAME.crt and add /home/pod/ssl/$NAME/$DOMAIN_NAME.key" @@ -47,9 +47,9 @@ echo "--> then restart nginx with sudo /etc/init.d/nginx restart" echo "****************" echo "Activation du service uwsgi" -mv ./$NAME/uwsgi-pod_tenant.service ./$NAME/uwsgi-pod_$NAME.service -sed -i "s/__NAME__/$NAME/g" ./$NAME/uwsgi-pod_$NAME.service -sudo ln -s $BASEDIR/$NAME/uwsgi-pod_$NAME.service /etc/systemd/system/uwsgi-pod_$NAME.service +mv "./$NAME/uwsgi-pod_tenant.service" "./$NAME/uwsgi-pod_$NAME.service" +sed -i "s/__NAME__/$NAME/g" "./$NAME/uwsgi-pod_$NAME.service" +sudo ln -s "$BASEDIR/$NAME/uwsgi-pod_$NAME.service" "/etc/systemd/system/uwsgi-pod_$NAME.service" sudo systemctl enable uwsgi-pod_$NAME echo "****************" echo "--> after restarting nginx, use $> sudo systemctl start uwsgi-pod_$NAME" @@ -61,35 +61,35 @@ sudo rabbitmqctl add_vhost rabbitpod-$NAME sudo rabbitmqctl set_permissions -p rabbitpod-$NAME pod ".*" ".*" ".*" echo "Données initiales" -sed -i "s/__NAME__/$NAME/g" ./$NAME/initial_data.json -sed -i "s/__ID_SITE__/$ID_SITE/g" ./$NAME/initial_data.json -sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" ./$NAME/initial_data.json +sed -i "s/__NAME__/$NAME/g" "./$NAME/initial_data.json" +sed -i "s/__ID_SITE__/$ID_SITE/g" "./$NAME/initial_data.json" +sed -i "s/__DOMAIN_NAME__/$DOMAIN_NAME/g" "./$NAME/initial_data.json" echo "--" echo "Pour intégrer les données en base concernant ce nouveau site, il faut lancer la commande suivante:" -echo "(django_pod) pod@pod:/usr/local/django_projects/podv3$ python manage.py loaddata pod/custom/tenants/$NAME/initial_data.json --settings=pod.custom.tenants.$NAME."$NAME"_settings" +echo "(django_pod) pod@pod:/usr/local/django_projects/podv3$ python manage.py loaddata pod/custom/tenants/$NAME/initial_data.json --settings=pod.custom.tenants.$NAME.\"$NAME\"_settings" echo "--" echo "N'oubliez pas de créer l'index dans elasticseach via cette commande :" echo "(django_pod) pod@pod:/usr/local/django_projects/podv3$ python manage.py create_pod_index --settings=pod.custom.tenants.$NAME."$NAME"_settings" echo "--" echo "crontab" echo "clear session" -echo "" >> $BASEDIR/sh_tenants/clearsessions.sh -echo "# $ID_SITE $NAME " >> $BASEDIR/sh_tenants/clearsessions.sh -echo "cd /usr/local/django_projects/podv3 && /home/pod/.virtualenvs/django_pod/bin/python manage.py clearsessions --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv3/pod/log/cron_clearsessions_$NAME.log 2>&1" >> $BASEDIR/clearsessions.sh +echo "" >> "$BASEDIR/sh_tenants/clearsessions.sh" +echo "# $ID_SITE $NAME " >> "$BASEDIR/sh_tenants/clearsessions.sh" +echo "cd /usr/local/django_projects/podv3 && /home/pod/.virtualenvs/django_pod/bin/python manage.py clearsessions --settings=pod.custom.tenants.$NAME.\"$NAME\"_settings &>> /usr/local/django_projects/podv3/pod/log/cron_clearsessions_$NAME.log 2>&1" >> "$BASEDIR/clearsessions.sh" echo "index videos" -echo "" >> $BASEDIR/sh_tenants/index_videos.sh -echo "# $ID_SITE $NAME " >> $BASEDIR/sh_tenants/index_videos.sh -echo "cd /usr/local/django_projects/podv3 && /home/pod/.virtualenvs/django_pod/bin/python manage.py index_videos --all --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv3/pod/log/cron_index_$NAME.log 2>&1" >> $BASEDIR/index_videos.sh +echo "" >> "$BASEDIR/sh_tenants/index_videos.sh" +echo "# $ID_SITE $NAME " >> "$BASEDIR/sh_tenants/index_videos.sh" +echo "cd /usr/local/django_projects/podv3 && /home/pod/.virtualenvs/django_pod/bin/python manage.py index_videos --all --settings=pod.custom.tenants.$NAME.\"$NAME\"_settings &>> /usr/local/django_projects/podv3/pod/log/cron_index_$NAME.log 2>&1" >> "$BASEDIR/index_videos.sh" echo "check_obsolete_videos" -echo "" >> $BASEDIR/sh_tenants/check_obsolete_videos.sh -echo "# $ID_SITE $NAME " >> $BASEDIR/sh_tenants/check_obsolete_videos.sh -echo "cd /usr/local/django_projects/podv3 && /home/pod/.virtualenvs/django_pod/bin/python manage.py check_obsolete_videos --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv3/pod/log/cron_obsolete_$NAME.log 2>&1" >> $BASEDIR/check_obsolete_videos.sh +echo "" >> "$BASEDIR/sh_tenants/check_obsolete_videos.sh" +echo "# $ID_SITE $NAME " >> "$BASEDIR/sh_tenants/check_obsolete_videos.sh" +echo "cd /usr/local/django_projects/podv3 && /home/pod/.virtualenvs/django_pod/bin/python manage.py check_obsolete_videos --settings=pod.custom.tenants.$NAME.\"$NAME\"_settings &>> /usr/local/django_projects/podv3/pod/log/cron_obsolete_$NAME.log 2>&1" >> "$BASEDIR/check_obsolete_videos.sh" echo "live_viewcounter" -echo "" >> $BASEDIR/sh_tenants/live_viewcounter.sh -echo "# $ID_SITE $NAME " >> $BASEDIR/sh_tenants/live_viewcounter.sh -echo "cd /usr/local/django_projects/podv3 && /home/pod/.virtualenvs/django_pod/bin/python manage.py live_viewcounter --settings=pod.custom.tenants.$NAME."$NAME"_settings &>> /usr/local/django_projects/podv3/pod/log/cron_viewcounter_$NAME.log 2>&1" >> $BASEDIR/live_viewcounter.sh +echo "" >> "$BASEDIR/sh_tenants/live_viewcounter.sh" +echo "# $ID_SITE $NAME " >> "$BASEDIR/sh_tenants/live_viewcounter.sh" +echo "cd /usr/local/django_projects/podv3 && /home/pod/.virtualenvs/django_pod/bin/python manage.py live_viewcounter --settings=pod.custom.tenants.$NAME.\"$NAME\"_settings &>> /usr/local/django_projects/podv3/pod/log/cron_viewcounter_$NAME.log 2>&1" >> "$BASEDIR/live_viewcounter.sh" -echo "FIN" +echo "FIN" diff --git a/pod/custom/tenants/remove_tenant.sh b/pod/custom/tenants/remove_tenant.sh index 49f915e71f..c6dc62cc82 100644 --- a/pod/custom/tenants/remove_tenant.sh +++ b/pod/custom/tenants/remove_tenant.sh @@ -4,8 +4,8 @@ NAME=$1 echo "remove previous tenant $NAME" -rm -rf ./$NAME -sudo systemctl stop uwsgi-pod_$NAME -sudo rm -f /etc/systemd/system/uwsgi-pod_$NAME.service -sudo rm -f /etc/nginx/sites-enabled/pod_nginx_$NAME.conf -sudo rabbitmqctl delete_vhost rabbitpod-$NAME \ No newline at end of file +rm -rf "./$NAME" +sudo systemctl stop "uwsgi-pod_$NAME" +sudo rm -f "/etc/systemd/system/uwsgi-pod_$NAME.service" +sudo rm -f "/etc/nginx/sites-enabled/pod_nginx_$NAME.conf" +sudo rabbitmqctl delete_vhost "rabbitpod-$NAME" diff --git a/pod/cut/static/js/video_cut.js b/pod/cut/static/js/video_cut.js index d8fb8a7e42..683bff9d2d 100644 --- a/pod/cut/static/js/video_cut.js +++ b/pod/cut/static/js/video_cut.js @@ -1,18 +1,27 @@ +/** + * Esup-Pod Video Cut scripts + */ + +// Read-only globals defined in video-script.html +/* +global player +*/ + let sliderOne = document.getElementById("slider-1"); let sliderTwo = document.getElementById("slider-2"); let displayValOne = document.getElementById("range1"); let displayValTwo = document.getElementById("range2"); let minGap = 0; let sliderTrack = document.querySelector(".slider-track"); -start_time = parseInt(sliderOne.value); -button_reset = document.getElementById("reset"); -initialStart = sliderOne.value; -initialEnd = sliderTwo.value; +let start_time = parseInt(sliderOne.value, 10); +let button_reset = document.getElementById("reset"); +let initialStart = sliderOne.value; +let initialEnd = sliderTwo.value; // Set max value let sliderMaxValue = sliderOne.max; // Set min value -let sliderMinValue = sliderOne.min; +// let sliderMinValue = sliderOne.min; function doChangeForRange1() { // Do change for the start value with the "time range" @@ -45,8 +54,8 @@ displayValTwo.addEventListener("change", doChangeForRange2); function slideOne() { // Do change for the start value with the slider - if (parseInt(sliderTwo.value) - parseInt(sliderOne.value) <= minGap) { - sliderOne.value = parseInt(sliderTwo.value) - minGap; + if (parseInt(sliderTwo.value, 10) - parseInt(sliderOne.value, 10) <= minGap) { + sliderOne.value = parseInt(sliderTwo.value, 10) - minGap; } displayValOne.value = intToTime(sliderOne.value); fillColor(); @@ -55,8 +64,8 @@ function slideOne() { function slideTwo() { // Do change for the end value with the slider - if (parseInt(sliderTwo.value) - parseInt(sliderOne.value) <= minGap) { - sliderTwo.value = parseInt(sliderOne.value) + minGap; + if (parseInt(sliderTwo.value, 10) - parseInt(sliderOne.value, 10) <= minGap) { + sliderTwo.value = parseInt(sliderOne.value, 10) + minGap; } displayValTwo.value = intToTime(sliderTwo.value); fillColor(); @@ -65,8 +74,8 @@ function slideTwo() { function fillColor() { // Apply the changes - percent1 = (sliderOne.value / sliderMaxValue) * 100; - percent2 = (sliderTwo.value / sliderMaxValue) * 100; + let percent1 = (sliderOne.value / sliderMaxValue) * 100; + let percent2 = (sliderTwo.value / sliderMaxValue) * 100; sliderTrack.style.background = `linear-gradient(to right, #dadae5 ${percent1}% , #1f8389 ${percent1}% , #1f8389 ${percent2}%, #dadae5 ${percent2}%)`; calculation_total_time(); } @@ -113,7 +122,7 @@ function calculation_total_time() { (function (n) { var f = function (e) { var c = e.which || e.keyCode; - if (c == 13) { + if (c === 13) { e.preventDefault(); let ConfirmationModalID = document.getElementById("ConfirmationModal"); let ConfirmationModal = @@ -133,10 +142,10 @@ function calculation_total_time() { }; })("keydown"); -noPressEnter(displayValOne); -noPressEnter(displayValTwo); -noPressEnter(sliderOne); -noPressEnter(sliderTwo); +window.noPressEnter(displayValOne); +window.noPressEnter(displayValTwo); +window.noPressEnter(sliderOne); +window.noPressEnter(sliderTwo); // Buttons to get the time of the video player let button_start = document.getElementById("button_start"); @@ -161,7 +170,7 @@ button_end.addEventListener("keydown", function (event) { */ function get_video_player_start(event) { event.preventDefault(); - time = Math.trunc(player.currentTime()) + start_time; + let time = Math.trunc(player.currentTime()) + start_time; displayValOne.value = intToTime(time); sliderOne.value = time; fillColor(); @@ -172,7 +181,7 @@ function get_video_player_start(event) { */ function get_video_player_end(event) { event.preventDefault(); - time = Math.trunc(player.currentTime()) + start_time; + let time = Math.trunc(player.currentTime()) + start_time; displayValTwo.value = intToTime(time); sliderTwo.value = Math.trunc(time); fillColor(); diff --git a/pod/enrichment/static/js/enrichment.js b/pod/enrichment/static/js/enrichment.js index ebd06c0d16..b4f875750d 100644 --- a/pod/enrichment/static/js/enrichment.js +++ b/pod/enrichment/static/js/enrichment.js @@ -2,6 +2,16 @@ * @file Esup-Pod functions for enrichment view. */ +// Read-only globals defined in video-script.html +/* +global player +*/ + +// Read-only globals defined in main.js +/* +global fadeIn +*/ + var id_form = "form_enrich"; function removeLoadedScript(lib) { @@ -53,21 +63,6 @@ function show_form(data) { enrich_type(); } -var showalert = function (message, alerttype) { - document.body.append( - '', - ); - setTimeout(function () { - document.getElementById("formalertdiv").remove(); - }, 5000); -}; - var ajaxfail = function (data) { showalert( gettext("Error getting form.") + @@ -84,6 +79,9 @@ var ajaxfail = function (data) { show_form(""); }; +/** + * Handle the "cancel enrichment" button. + */ document.addEventListener("click", (e) => { if (e.target.id != "cancel_enrichment") return; document.querySelectorAll("form.get_form").forEach((form) => { @@ -92,6 +90,9 @@ document.addEventListener("click", (e) => { }); }); +/** + * Handle any of the "get_form" submitting forms. + */ document.addEventListener("submit", (e) => { if (!e.target.classList.contains("get_form")) return; e.preventDefault(); @@ -99,6 +100,10 @@ document.addEventListener("submit", (e) => { sendandgetform(e.target, action); }); + +/** + * Handle the "form_save" form submit. + */ document.addEventListener("submit", (e) => { if (!e.target.classList.contains("form_save")) return; e.preventDefault(); @@ -124,7 +129,7 @@ var sendandgetform = async function (elt, action) { }); const data = await response.text(); if ( - data.indexOf(id_form) == -1 && + data.indexOf(id_form) === -1 && (action === "new" || action === "modify") ) { showalert( @@ -140,7 +145,7 @@ var sendandgetform = async function (elt, action) { show_form(data); elt.classList.add("info"); } else if (action === "delete") { - if (data.indexOf("list_enrichment") == -1) { + if (data.indexOf("list_enrichment") === 1) { showalert( gettext("You are no longer authenticated. Please log in again."), "alert-danger", @@ -186,8 +191,8 @@ var sendform = async function (elt, action) { .then((response) => response.text()) .then((data) => { if ( - data.indexOf("list_enrichment") == -1 && - data.indexOf("form") == -1 + data.indexOf("list_enrichment") === -1 && + data.indexOf("form") === -1 ) { showalert( gettext("You are no longer authenticated. Please log in again."), @@ -229,7 +234,7 @@ var sendform = async function (elt, action) { }) .then((response) => response.text()) .then((data) => { - if (data.indexOf("list_enrichment") == -1) { + if (data.indexOf("list_enrichment") === -1) { showalert( gettext("You are no longer authenticated. Please log in again."), "alert-danger", @@ -273,6 +278,12 @@ Number.prototype.toHHMMSS = function () { return hours + ":" + minutes + ":" + seconds; }; +/** + * UNUSED function ?? + * TODO : remove in 3.8.0 + * @param {*} data + */ +/* function get_form(data) { var form = document.getElementById("form_enrich"); form.style.display = "none"; @@ -308,7 +319,8 @@ function get_form(data) { ); enrich_type(); manageResize(); -} +}*/ + function enrich_type() { document.getElementById("id_image").closest("div.form-group").style.display = "none"; @@ -340,7 +352,7 @@ const setTimecode = (e) => { const timecodeSpan = parentNode.querySelector( "div.getfromvideo span.timecode", ); - timecodeSpan.innerHTML = " " + parseInt(e.target.value).toHHMMSS(); + timecodeSpan.textContent = " " + parseInt(e.target.value, 10).toHHMMSS(); }; document.addEventListener("change", setTimecode); @@ -350,13 +362,13 @@ document.addEventListener("click", (e) => { if (!(typeof player === "undefined")) { if (e.target.getAttribute("id") == "getfromvideo_start") { let inputStart = document.getElementById("id_start"); - inputStart.value = parseInt(player.currentTime()); - changeEvent = new Event("change"); + inputStart.value = parseInt(player.currentTime(), 10); + let changeEvent = new Event("change"); inputStart.dispatchEvent(changeEvent); } else { let inputEnd = document.getElementById("id_end"); - inputEnd.value = parseInt(player.currentTime()); - changeEvent = new Event("change"); + inputEnd.value = parseInt(player.currentTime(), 10); + let changeEvent = new Event("change"); inputEnd.dispatchEvent(changeEvent); } } @@ -438,9 +450,9 @@ function verify_fields() { if (richtext.value == "") { richtext.insertAdjacentHTML( "beforebegin", - "   " + + "" + gettext("Please enter a correct richtext.") + - "", + "", ); richtext.closest("div.form-group").classList.add("has-error"); @@ -532,8 +544,8 @@ function verify_fields() { /*** Verify if fields end and start are correct ***/ function verify_end_start_items() { var msg = ""; - new_start = parseInt(document.getElementById("id_start").value); - new_end = parseInt(document.getElementById("id_end").value); + let new_start = parseInt(document.getElementById("id_start").value, 10); + let new_end = parseInt(document.getElementById("id_end").value, 10); if (new_start > new_end) { msg = gettext("The start field value is greater than the end field one."); } else if (new_end > video_duration) { @@ -549,13 +561,13 @@ function verify_end_start_items() { /*** Verify if there is a overlap with over enrich***/ function overlaptest() { //var video_list_enrich=[]; - var new_start = parseInt(document.getElementById("id_start").value); - var new_end = parseInt(document.getElementById("id_end").value); + var new_start = parseInt(document.getElementById("id_start").value, 10); + var new_end = parseInt(document.getElementById("id_end").value, 10); var id = document.getElementById("id_enrich").value; var msg = ""; document.querySelectorAll("ul#slides li").forEach((e) => { - var data_start = parseInt(e.dataset.start); - var data_end = parseInt(e.dataset.end); + var data_start = parseInt(e.dataset.start, 10); + var data_end = parseInt(e.dataset.end, 10); if ( id != e.dataset.id && !( diff --git a/pod/enrichment/static/js/videojs-slides.js b/pod/enrichment/static/js/videojs-slides.js index 69a95454d4..04a514441f 100644 --- a/pod/enrichment/static/js/videojs-slides.js +++ b/pod/enrichment/static/js/videojs-slides.js @@ -1,5 +1,22 @@ "use-strict"; +/** + * Esup-Pod videojs Slides plugin +*/ +// Read-only globals defined in video-script.html +/* +global player +*/ +// Read-only globals defined in IE +/* +global ActiveXObject +*/ + //// Begin Safari patch for enrich track +/** + * Safari patch for enrich track + * @param {*} url + * @param {*} callback + */ var loadEnrichmentVTTfile = function (url, callback) { var getXhr = function () { try { @@ -31,7 +48,7 @@ var loadEnrichmentVTTfile = function (url, callback) { timeInSecond = function (strtime) { let atime = strtime.split(":"); return ( - parseInt(atime[0]) * 3600 + parseInt(atime[1]) * 60 + parseInt(atime[2]) + parseInt(atime[0], 10) * 3600 + parseInt(atime[1], 10) * 60 + parseInt(atime[2], 10) ); }, createEmptyCue = function (start, end) { @@ -55,11 +72,11 @@ var loadEnrichmentVTTfile = function (url, callback) { if (null != xhr) { xhr.open("GET", url, true); xhr.onreadystatechange = function () { - if (this.readyState == 4) { + if (this.readyState === 4) { var lines = xhr.responseText.split("\n"), nbl = lines.length, reg = - /(^\d{2}:\d{2}:\d{2})\.\d{3} \-\-> (\d{2}:\d{2}:\d{2})\.\d{3}$/i, + /(^\d{2}:\d{2}:\d{2})\.\d{3} --> (\d{2}:\d{2}:\d{2})\.\d{3}$/i, fisrtcueline = 1, txtdata = "", c = 0; @@ -70,19 +87,23 @@ var loadEnrichmentVTTfile = function (url, callback) { } //// Get First cue line and create first cue for (let i = 1; i < nbl; i++) { - if ((m = lines[i].match(reg))) { + let m = lines[i].match(reg); + if (m) { fisrtcueline = i; - cues[c] = createEmptyCue(timeInSecond(m[1]), timeInSecond(m[2])); //console.log('Cue '+c+' is created'); + cues[c] = createEmptyCue(timeInSecond(m[1]), timeInSecond(m[2])); + //console.log('Cue '+c+' is created'); break; } } //// Read next lines, feed first, create and feed next cues for (let i = fisrtcueline + 1; i < nbl; i++) { - if ((m = lines[i].match(reg))) { + let m = lines[i].match(reg); + if (m) { setCue(c, JSON.parse(txtdata.split("}")[0] + "}")); txtdata = ""; c++; - cues[c] = createEmptyCue(timeInSecond(m[1]), timeInSecond(m[2])); //console.log('Cue '+c+' is created'); + cues[c] = createEmptyCue(timeInSecond(m[1]), timeInSecond(m[2])); + //console.log('Cue '+c+' is created'); } else { txtdata += lines[i]; } @@ -111,7 +132,7 @@ const slide_color = { weblink: "var(--bs-red)", embed: "var(--bs-green)", }; -//Is now a list of css class instead of video/slide width values +// Is now a list of css class instead of video/slide width values const split_slide_label = gettext("Split view"); const split_slide = "split-slide"; const no_slide_label = gettext("slide off"); @@ -302,9 +323,9 @@ var VideoSlides = function (items) { var keys = Object.keys(this.slidesItems); for (let i = 0; i <= keys.length - 1; i++) { var slidebar_left = - (parseInt(this.slidesItems[i].start) / duration) * 100; + (parseInt(this.slidesItems[i].start, 10) / duration) * 100; var slidebar_width = - (parseInt(this.slidesItems[i].end) / duration) * 100 - slidebar_left; + (parseInt(this.slidesItems[i].end, 10) / duration) * 100 - slidebar_left; var id = this.slidesItems[i].id; var type = this.slidesItems[i].type; var newslide = document.createElement("div"); @@ -357,7 +378,6 @@ var VideoSlides = function (items) { class SlideMode extends vjs_menu_item { constructor(player, options) { options = options || {}; - options.label = options.label; super(player, options); this.on("click", this.onClick); this.addClass("vjs-slide-mode"); @@ -374,7 +394,7 @@ var VideoSlides = function (items) { var available = document.getElementsByClassName("vjs-slide-mode"); for (let i = 0, nb = available.length, e; i < nb; i++) { //for (let e of available) { - let e = available[i]; + e = available[i]; if (e.firstChild.innerHTML != this.el().firstChild.innerHTML) { e.setAttribute("aria-checked", false); e.classList.remove("vjs-selected"); diff --git a/pod/enrichment/templates/enrichment/edit_enrichment.html b/pod/enrichment/templates/enrichment/edit_enrichment.html index 84c8d95de8..ee7600df6f 100644 --- a/pod/enrichment/templates/enrichment/edit_enrichment.html +++ b/pod/enrichment/templates/enrichment/edit_enrichment.html @@ -95,20 +95,6 @@

let name = ''; /*** For the form ***/; let video_duration = {{video.duration}}; - /*** For the Cancel button ***/ - - document.addEventListener("reset", (e) => { - if (e.target !== document.querySelector("#page-video form")) return - document.getElementById("form_enrich").innerHTML = ""; - document.querySelectorAll("form").forEach((form)=>{ - form.style.display = "block"; - }) - document.querySelectorAll("table tr").forEach((tr) => { - tr.classList.remove("info"); - }); - - manageResize(); - }); {{block.super}} diff --git a/pod/podfile/models.py b/pod/podfile/models.py index 66914d8121..b58424c838 100644 --- a/pod/podfile/models.py +++ b/pod/podfile/models.py @@ -53,7 +53,7 @@ class Meta: ordering = ["name"] app_label = "podfile" - def clean(self): + def clean(self) -> None: if self.name == "Home": same_home = UserFolder.objects.filter(owner=self.owner, name="Home") if same_home: @@ -61,16 +61,16 @@ def clean(self): "A user cannot have have multiple home directories." ) - def __str__(self): + def __str__(self) -> str: return "{0}".format(self.name) - def get_all_files(self): + def get_all_files(self) -> list: file_list = self.customfilemodel_set.all() image_list = self.customimagemodel_set.all() result_list = sorted(chain(image_list, file_list), key=attrgetter("uploaded_at")) return result_list - def delete(self): + def delete(self) -> None: for file in self.customfilemodel_set.all(): file.delete() for img in self.customimagemodel_set.all(): @@ -79,7 +79,7 @@ def delete(self): @receiver(post_save, sender=User) -def create_owner_directory(sender, instance, created, **kwargs): +def create_owner_directory(sender, instance, created: bool, **kwargs) -> None: if created: try: UserFolder.objects.create(owner=instance, name="home") @@ -91,7 +91,7 @@ def create_owner_directory(sender, instance, created, **kwargs): print(msg) -def get_upload_path_files(instance, filename): +def get_upload_path_files(instance, filename) -> str: user_rep = ( instance.created_by.owner.hashkey if (instance.created_by.owner) @@ -125,16 +125,16 @@ class BaseFileModel(models.Model): on_delete=models.CASCADE, ) - def save(self, **kwargs): + def save(self, **kwargs) -> None: path, ext = os.path.splitext(self.file.name) # if not self.name or self.name == "": self.name = os.path.basename(path) return super(BaseFileModel, self).save(**kwargs) - def class_name(self): + def class_name(self) -> str: return self.__class__.__name__ - def file_exist(self): + def file_exist(self) -> bool: return self.file and os.path.isfile(self.file.path) class Meta: @@ -146,11 +146,11 @@ class CustomFileModel(BaseFileModel): file = models.FileField(upload_to=get_upload_path_files, max_length=255) @property - def file_ext(self): + def file_ext(self) -> str: return self.file.path.rpartition(".")[-1].lower() @property - def file_type(self): + def file_type(self) -> str: filetype = mimetypes.guess_type(self.file.path)[0] if filetype is None: filetype = self.file_ext @@ -159,18 +159,18 @@ def file_type(self): file_type.fget.short_description = _("Get the file type") @property - def file_size(self): + def file_size(self) -> int: return os.path.getsize(self.file.path) file_size.fget.short_description = _("Get the file size") - def delete(self): + def delete(self) -> None: if self.file: if os.path.isfile(self.file.path): os.remove(self.file.path) super(CustomFileModel, self).delete() - def __str__(self): + def __str__(self) -> str: if self.file and os.path.isfile(self.file.path): return "%s (%s, %s)" % (self.name, self.file_type, self.file_size) else: @@ -186,7 +186,7 @@ class CustomImageModel(BaseFileModel): file = models.ImageField(upload_to=get_upload_path_files, max_length=255) @property - def file_type(self): + def file_type(self) -> str: filetype = mimetypes.guess_type(self.file.path)[0] if filetype is None: fname, dot, extension = self.file.path.rpartition(".") @@ -196,12 +196,12 @@ def file_type(self): file_type.fget.short_description = _("Get the file type") @property - def file_size(self): + def file_size(self) -> int: return os.path.getsize(self.file.path) file_size.fget.short_description = _("Get the file size") - def delete(self): + def delete(self) -> None: if self.file: delete(self.file) if os.path.isfile(self.file.path): diff --git a/pod/podfile/static/podfile/js/filewidget.js b/pod/podfile/static/podfile/js/filewidget.js index b089ca536d..213a25a31f 100644 --- a/pod/podfile/static/podfile/js/filewidget.js +++ b/pod/podfile/static/podfile/js/filewidget.js @@ -66,7 +66,7 @@ if (typeof loaded == "undefined") { " "; document.getElementById("fileinput_" + id_input).innerHTML = html; - document.getElementById("modal-folder_" + id_input).innerHTML = ""; + document.getElementById("modal-folder_" + id_input).textContent = ""; let modalFile = bootstrap.Modal.getInstance( document.getElementById("fileModal_" + id_input) @@ -219,7 +219,7 @@ if (typeof loaded == "undefined") { break; default: // Extract info from data-* attributes document.getElementById("folderFormName").style.display = "block"; - document.getElementById("folderModalCenterTitle").innerHTML = gettext( + document.getElementById("folderModalCenterTitle").textContent = gettext( "Enter new name of folder" ); 4; @@ -316,7 +316,7 @@ if (typeof loaded == "undefined") { const formUserId = document.getElementById("formuserid"); if (!formUserId) return; - const folderId = Number.parseInt(formUserId.value); + const folderId = Number.parseInt(formUserId.value, 10); const add = gettext("Add"); const url = "/podfile/ajax_calls/search_share_user?term=" + searchTerm + "&foldid=" + folderId; const token = document.querySelector('input[name="csrfmiddlewaretoken"]').value; @@ -390,7 +390,7 @@ if (typeof loaded == "undefined") { cache: "no-cache", }) .then((response) => { - if (response.status == 201) { + if (response.status === 201) { reloadRemoveBtn(); } else { showalert( @@ -514,7 +514,7 @@ if (typeof loaded == "undefined") { if (folder_searching === true ) { return; } else { - if (text.length > 2 || text.length == 0) { + if (text.length > 2 || text.length === 0) { getFolders(text); } } @@ -529,7 +529,7 @@ if (typeof loaded == "undefined") { } else { let user_search = document.getElementById("user_search"); if (user_search) { - user_search.innerHTML = ""; + user_search.textContent = ""; fadeOut(user_search, 300); setTimeout(() => { user_search.hide(); @@ -543,7 +543,7 @@ if (typeof loaded == "undefined") { if (data.list_element) { var folder_id = data.folder_id; - if (data.new_folder == true) { + if (data.new_folder === true) { let type = document.getElementById("list_folders_sub").dataset.type; let string_html = @@ -750,7 +750,7 @@ if (typeof loaded == "undefined") { function getFolders(search = "") { - document.getElementById("list_folders_sub").innerHTML = ""; + document.getElementById("list_folders_sub").textContent = ""; let type = document.getElementById("list_folders_sub").dataset.type; let currentFolder = getCurrentSessionFolder(); let url = "/podfile/ajax_calls/user_folders"; diff --git a/pod/podfile/templates/podfile/customfilewidget.html b/pod/podfile/templates/podfile/customfilewidget.html index a86ff85198..f689290306 100644 --- a/pod/podfile/templates/podfile/customfilewidget.html +++ b/pod/podfile/templates/podfile/customfilewidget.html @@ -107,7 +107,7 @@

{% blocktrans count count var ownerFilter = {{ owner_filter|yesno:'true,false'}}; {% if videos.has_next %} - page = parseInt("{{videos.next_page_number}}"); + page = parseInt("{{videos.next_page_number}}", 10); nextPage = true; {% endif %} diff --git a/pod/video/tests/test_apps.py b/pod/video/tests/test_apps.py new file mode 100644 index 0000000000..94f6c76960 --- /dev/null +++ b/pod/video/tests/test_apps.py @@ -0,0 +1,77 @@ +"""Unit tests for Esup-Pod video apps. + +test with `python manage.py test pod.video.tests.test_apps` +""" + +from django.test import TestCase +from django.contrib.auth.models import User +from django.contrib.sites.models import Site +from django.core.exceptions import ObjectDoesNotExist + +from pod.video.models import Channel, Theme, Video, Type, Discipline +from pod.video_encode_transcript.models import VideoRendition +from pod.video.apps import set_default_site, update_video_passwords + +# ggignore-start +# gitguardian:ignore +PWD = "azerty1234" # nosec +# ggignore-end + + +class VideoTestApps(TestCase): + """TestCase for Esup-Pod video apps.""" + + fixtures = [ + "initial_data.json", + ] + + def setUp(self) -> None: + """Set up required objects for next tests.""" + self.user = User.objects.create(username="pod", password=PWD) # nosem + self.chan = Channel.objects.create(title="ChannelTest1") + self.theme = Theme.objects.create(title="Theme1", slug="blabla", channel=self.chan) + self.disc = Discipline.objects.create(title="Discipline1") + self.type = Type.objects.get(id=1) + self.vr = VideoRendition.objects.get(id=1) + self.vid = Video.objects.create( + title="Video1", + owner=self.user, + video="test.mp4", + is_draft=False, + password=PWD, # nosem + type=Type.objects.get(id=1) + ) + print("\n ---> SetUp of VideoTestApps: OK!") + + def test_set_default_site(self) -> None: + """Test the set_default_site function.""" + cur_site = Site.objects.get_current() + multi_sites = [self.vid, self.type, self.vr] + single_site = [self.chan, self.disc] + + for obj in multi_sites: + obj.sites.remove(cur_site) + self.assertNotIn(cur_site, obj.sites.all()) + for obj in single_site: + obj.site = None + obj.save() + obj.site + self.assertRaises(ObjectDoesNotExist) + + set_default_site(self) + + for obj in multi_sites: + self.assertIn(cur_site, obj.sites.all()) + for obj in single_site: + self.assertEqual(cur_site, obj.site) + + print(" ---> test_set_default_site of VideoTestApps: OK!") + + def test_update_video_passwords(self) -> None: + """Test the update_video_passwords function.""" + self.assertNotIn("pbkdf2_sha256$", self.vid.password) + + update_video_passwords(self) + self.vid = Video.objects.get(id=1) + + self.assertIn("pbkdf2_sha256$", self.vid.password) diff --git a/pod/video/tests/test_bulk_update.py b/pod/video/tests/test_bulk_update.py index 38f92d0a28..d6a8c36f27 100644 --- a/pod/video/tests/test_bulk_update.py +++ b/pod/video/tests/test_bulk_update.py @@ -23,7 +23,7 @@ class BulkUpdateTestCase(TransactionTestCase): ] serialized_rollback = True - def setUp(self): + def setUp(self) -> None: """Create videos to be tested.""" self.factory = RequestFactory() self.client = Client() @@ -122,7 +122,7 @@ def setUp(self): print(" ---> SetUp of BulkUpdateTestCase: OK!") - def test_bulk_update_type(self): + def test_bulk_update_type(self) -> None: """Test bulk update of type attribute.""" video1 = Video.objects.get(title="Video1") video2 = Video.objects.get(title="Video2") @@ -155,7 +155,7 @@ def test_bulk_update_type(self): print("---> test_bulk_update_type of BulkUpdateTestCase: OK") self.client.logout() - def test_bulk_update_tags(self): + def test_bulk_update_tags(self) -> None: """Test bulk update of tags attribute.""" video4 = Video.objects.get(title="Video4") video5 = Video.objects.get(title="Video5") diff --git a/pod/video/tests/test_feeds.py b/pod/video/tests/test_feeds.py index 4d33424fff..8ba8b96408 100644 --- a/pod/video/tests/test_feeds.py +++ b/pod/video/tests/test_feeds.py @@ -15,10 +15,10 @@ class FeedTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: pass - def test_get_rss_video_from_video(self): + def test_get_rss_video_from_video(self) -> None: self.client = Client() url = reverse("rss-video:rss-video", kwargs={}) response = self.client.get(url) @@ -28,7 +28,7 @@ def test_get_rss_video_from_video(self): self.assertTrue(mediapackage) print(" --> test_get_rss_video_from_video of FeedTestView", ": OK!") - def test_get_rss_audio_from_video(self): + def test_get_rss_audio_from_video(self) -> None: self.client = Client() url = reverse("rss-video:rss-audio", kwargs={}) response = self.client.get(url) diff --git a/pod/video/tests/test_obsolescence.py b/pod/video/tests/test_obsolescence.py index 3b94e96fc0..236795f09c 100644 --- a/pod/video/tests/test_obsolescence.py +++ b/pod/video/tests/test_obsolescence.py @@ -126,7 +126,7 @@ def setUp(self): print(" ---> SetUp of ObsolescenceTestCase: OK!") - def test_check_video_date_delete(self): + def test_check_video_date_delete(self) -> None: """Check that the videos deletion date complies with the settings.""" video = Video.objects.get(id=1) date1 = date.today() + timedelta(days=DEFAULT_YEAR_DATE_DELETE * 365) diff --git a/pod/video/tests/test_utils.py b/pod/video/tests/test_utils.py index ee84c4db7c..c0941c7277 100644 --- a/pod/video/tests/test_utils.py +++ b/pod/video/tests/test_utils.py @@ -5,10 +5,8 @@ from django.test import TestCase from django.contrib.auth.models import User -from pod.video.models import Channel, Theme -from pod.video.models import Video, Type -from pod.video.utils import pagination_data, get_headband -from pod.video.utils import change_owner, get_videos +from pod.video.models import Channel, Theme, Video, Type +from pod.video.utils import pagination_data, get_headband, change_owner, get_videos class VideoTestUtils(TestCase): @@ -18,7 +16,7 @@ class VideoTestUtils(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Set up required objects for next tests.""" self.user = User.objects.create(username="pod", password="pod1234pod") self.user2 = User.objects.create(username="pod2", password="pod1234pod2") @@ -32,7 +30,7 @@ def setUp(self): type=Type.objects.get(id=1), ) - def test_pagination_data(self): + def test_pagination_data(self) -> None: # First data, previous_url = None data_length = 9 offset = 0 @@ -56,18 +54,18 @@ def test_pagination_data(self): actual = pagination_data(path, offset, limit, data_length) self.assertEqual(actual, expected) - def test_get_headband(self): + def test_get_headband(self) -> None: self.assertEqual(get_headband(self.c).get("type", None), "channel") self.assertEqual(get_headband(self.c, self.theme).get("type", None), "theme") - def test_change_owner(self): + def test_change_owner(self) -> None: """Change video owner.""" actual = change_owner(str(self.v.id), self.user2) self.assertEqual(actual, True) # Must return false with non-existent video id self.assertEqual(change_owner(100, self.user2), False) - def test_get_videos(self): + def test_get_videos(self) -> None: actual = get_videos(self.v.title, self.user.id) expected = { "count": 1, diff --git a/pod/video/tests/test_views.py b/pod/video/tests/test_views.py index eeb07cb1df..7f0b3f988d 100644 --- a/pod/video/tests/test_views.py +++ b/pod/video/tests/test_views.py @@ -45,7 +45,7 @@ class ChannelTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Set up some test channels.""" site = Site.objects.get(id=1) self.c = Channel.objects.create(title="ChannelTest1") @@ -77,7 +77,7 @@ def setUp(self): self.c2.site = site @override_settings(ORGANIZE_BY_THEME=False) - def test_get_channel_view(self): + def test_get_channel_view(self) -> None: reload(views) self.client = Client() response = self.client.get("/%s/" % self.c.slug) @@ -97,7 +97,7 @@ def test_get_channel_view(self): print(" ---> test_channel_with_theme_in_argument of ChannelTestView: OK!") @override_settings(ORGANIZE_BY_THEME=True) - def test_regroup_videos_by_theme(self): + def test_regroup_videos_by_theme(self) -> None: reload(views) # Test get videos and theme from channel view self.client = Client() @@ -192,7 +192,7 @@ class MyChannelsTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") user2 = User.objects.create(username="pod2", password="pod1234pod") @@ -210,7 +210,7 @@ def setUp(self): user2.owner.save() print(" ---> SetUp of MyChannelsTestView: OK!") - def test_get_mychannels_view(self): + def test_get_mychannels_view(self) -> None: self.client = Client() self.user = User.objects.get(username="pod") self.client.force_login(self.user) @@ -231,7 +231,7 @@ class ChannelEditTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") user2 = User.objects.create(username="pod2", password="pod1234pod") @@ -247,7 +247,7 @@ def setUp(self): print(" ---> SetUp of ChannelEditTestView: OK!") - def test_channel_edit_get_request(self): + def test_channel_edit_get_request(self) -> None: self.client = Client() channel = Channel.objects.get(title="ChannelTest1") self.user = User.objects.get(username="pod") @@ -265,7 +265,7 @@ def test_channel_edit_get_request(self): self.assertEqual(response.status_code, 403) print(" ---> test_channel_edit_get_request of ChannelEditTestView: OK!") - def test_channel_edit_post_request(self): + def test_channel_edit_post_request(self) -> None: self.client = Client() channel = Channel.objects.get(title="ChannelTest1") self.user = User.objects.get(username="pod") @@ -288,7 +288,7 @@ class ThemeEditTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") c1 = Channel.objects.create(title="ChannelTest1") @@ -314,7 +314,7 @@ def setUp(self): user.owner.save() print(" ---> SetUp of ThemeEditTestView: OK!") - def test_theme_edit_get_request(self): + def test_theme_edit_get_request(self) -> None: self.client = Client() channel = Channel.objects.get(title="ChannelTest1") self.user = User.objects.get(username="pod") @@ -327,7 +327,7 @@ def test_theme_edit_get_request(self): self.assertEqual(channel.themes.all().count(), 3) print(" ---> test_theme_edit_get_request of ThemeEditTestView: OK!") - def test_theme_edit_post_request(self): + def test_theme_edit_post_request(self) -> None: self.client = Client() channel = Channel.objects.get(title="ChannelTest1") self.user = User.objects.get(username="pod") @@ -411,7 +411,7 @@ class DashboardTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") user2 = User.objects.create(username="pod2", password="pod1234pod") @@ -454,7 +454,7 @@ def setUp(self): print(" ---> SetUp of MyChannelsTestView: OK!") - def test_get_dashboard_view(self): + def test_get_dashboard_view(self) -> None: self.client = Client() self.user = User.objects.get(username="pod") self.client.force_login(self.user) @@ -475,7 +475,7 @@ class VideosTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: # type, discipline, owner et tag user = User.objects.create(username="pod", password="pod1234pod") user2 = User.objects.create(username="pod2", password="pod1234pod") @@ -538,7 +538,7 @@ def setUp(self): print(" ---> SetUp of VideosTestView: OK!") @override_settings(HIDE_USER_FILTER=False) - def test_get_videos_view(self): + def test_get_videos_view(self) -> None: self.client = Client() url = reverse("videos:videos") response = self.client.get(url) @@ -596,7 +596,7 @@ class VideoTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: # type, discipline, owner et tag site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") @@ -631,7 +631,7 @@ def setUp(self): user3.owner.sites.add(Site.objects.get_current()) user3.owner.save() - def test_video_get_request(self): + def test_video_get_request(self) -> None: v = Video.objects.get(title="Video1") self.assertEqual(v.is_draft, True) # test draft @@ -757,7 +757,7 @@ class VideoEditTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") user2 = User.objects.create(username="pod2", password="pod1234pod") @@ -799,7 +799,7 @@ def setUp(self): print(" ---> SetUp of VideoEditTestView: OK!") - def test_video_edit_get_request(self): + def test_video_edit_get_request(self) -> None: self.client = Client() url = reverse("video:video_edit", kwargs={}) response = self.client.get(url) @@ -837,7 +837,7 @@ def test_video_edit_get_request(self): self.assertEqual(response.status_code, 403) print(" ---> test_video_edit_get_request of VideoEditTestView: OK!") - def test_video_edit_post_request(self): + def test_video_edit_post_request(self) -> None: self.client = Client() video = Video.objects.get(title="Video1") self.user = User.objects.get(username="pod") @@ -950,7 +950,7 @@ class video_deleteTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") user2 = User.objects.create(username="pod2", password="pod1234pod") @@ -987,7 +987,7 @@ def setUp(self): user2.owner.save() print(" ---> SetUp of video_deleteTestView: OK!") - def test_video_delete_get_request(self): + def test_video_delete_get_request(self) -> None: self.client = Client() video = Video.objects.get(title="Video1") url = reverse("video:video_delete", kwargs={"slug": video.slug}) @@ -1015,7 +1015,7 @@ def test_video_delete_get_request(self): self.user = User.objects.get(username="pod") print(" ---> test_video_edit_get_request of video_deleteTestView: OK!") - def test_video_delete_post_request(self): + def test_video_delete_post_request(self) -> None: self.client = Client() video = Video.objects.get(title="Video1") self.user = User.objects.get(username="pod") @@ -1049,7 +1049,7 @@ class video_notesTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: site = Site.objects.get(id=1) user = User.objects.create(username="pod", password="pod1234pod") v = Video.objects.create( @@ -1064,7 +1064,7 @@ def setUp(self): user.owner.save() print(" ---> SetUp of video_notesTestView: OK!") - def test_video_notesTestView_get_request(self): + def test_video_notesTestView_get_request(self) -> None: self.client = Client() video = Video.objects.get(title="Video1") self.user = User.objects.get(username="pod") @@ -1086,7 +1086,7 @@ def test_video_notesTestView_get_request(self): # self.assertEqual(response.context['notesForm'].instance, note) print(" ---> test_video_notesTestView_get_request of video_notesTestView: OK!") - def test_video_notesTestView_post_request(self): + def test_video_notesTestView_post_request(self) -> None: self.client = Client() video = Video.objects.get(title="Video1") self.user = User.objects.get(username="pod") @@ -1126,7 +1126,7 @@ class video_countTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: user = User.objects.create(username="pod", password="pod1234pod") Video.objects.create( title="Video1", @@ -1136,7 +1136,7 @@ def setUp(self): ) print(" ---> SetUp of video_countTestView: OK!") - def test_video_countTestView_get_request(self): + def test_video_countTestView_get_request(self) -> None: self.client = Client() video = Video.objects.get(title="Video1") url = reverse("video:video_count", kwargs={"id": 2}) @@ -1147,7 +1147,7 @@ def test_video_countTestView_get_request(self): self.assertEqual(response.status_code, 403) print(" ---> test_video_countTestView_get_request of video_countTestView: OK!") - def test_video_countTestView_post_request(self): + def test_video_countTestView_post_request(self) -> None: self.client = Client() video = Video.objects.get(title="Video1") print("count: %s" % video.get_viewcount()) @@ -1177,7 +1177,7 @@ def setUp(self): ) print(" ---> SetUp of VideoMarkerTestView: OK!") - def test_video_markerTestView_get_request(self): + def test_video_markerTestView_get_request(self) -> None: # anonyme self.client = Client() video = Video.objects.get(title="Video1") @@ -1210,7 +1210,7 @@ class VideoTestUpdateOwner(TransactionTestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: self.client = Client() self.admin = User.objects.create( @@ -1237,7 +1237,7 @@ def setUp(self): ) print(" ---> SetUp of VideoTestUpdateOwner: OK!") - def test_update_video_owner(self): + def test_update_video_owner(self) -> None: """Test update video owner.""" url = reverse("video:update_video_owner", kwargs={"user_id": self.admin.id}) @@ -1306,7 +1306,7 @@ class VideoTestFiltersViews(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: self.client = Client() self.admin = User.objects.create( @@ -1333,7 +1333,7 @@ def setUp(self): ) print(" ---> SetUp of VideoTestFiltersViews: OK!") - def test_filter_owners(self): + def test_filter_owners(self) -> None: url = reverse("video:filter_owners") # Authentication required @@ -1387,7 +1387,7 @@ def test_filter_owners(self): self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(json.loads(response.content.decode("utf-8")), expected) - def test_filter_videos(self): + def test_filter_videos(self) -> None: url = reverse("video:filter_videos", kwargs={"user_id": self.admin.id}) # Authentication required @@ -1433,7 +1433,7 @@ def test_filter_videos(self): self.assertEqual(response.status_code, HTTPStatus.OK) self.assertEqual(json.loads(response.content.decode("utf-8")), expected) - def tearDown(self): + def tearDown(self) -> None: super(VideoTestFiltersViews, self).tearDown() @@ -1449,7 +1449,7 @@ class ChannelJsonResponseViews(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Set up the tests.""" self.site = Site.objects.get(id=1) self.user = User.objects.create( @@ -1481,7 +1481,7 @@ def setUp(self): self.video.channel.add(self.second_channel) @override_settings(HIDE_CHANNEL_TAB=False) - def test_get_channels_for_navbar(self): + def test_get_channels_for_navbar(self) -> None: """Test if the get channels request for the navbar works correctly.""" response = self.client.get( f"{reverse('video:get-channels-for-specific-channel-tab')}" @@ -1504,7 +1504,7 @@ def test_get_channels_for_navbar(self): print(" ---> test_get_channels_for_navbar: OK!") @override_settings(HIDE_CHANNEL_TAB=False) - def test_get_channel_tabs_for_navbar(self): + def test_get_channel_tabs_for_navbar(self) -> None: """Test if the get channel tabs request for the navbar works correctly.""" response = self.client.get(reverse("video:get-channel-tabs")) self.assertEqual( @@ -1525,7 +1525,7 @@ def test_get_channel_tabs_for_navbar(self): print(" ---> test_get_channel_tabs_for_navbar: OK!") @override_settings(HIDE_CHANNEL_TAB=False) - def test_get_channels_for_specific_channel_tab(self): + def test_get_channels_for_specific_channel_tab(self) -> None: """Test if the get channels request for a specific channel tab works correctly.""" response = self.client.get( f"{reverse('video:get-channels-for-specific-channel-tab')}?page=1&id=1" @@ -1555,7 +1555,7 @@ class VideoTranscriptTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Set up data for test class.""" user = User.objects.create(username="pod", password="pod1234pod") Video.objects.create( @@ -1566,7 +1566,7 @@ def setUp(self): ) print(" ---> SetUp of VideoTranscriptTestView: OK!") - def test_video_transcript_get_request(self): + def test_video_transcript_get_request(self) -> None: """Check response for get request with default settings.""" # anonyme self.client = Client() @@ -1603,11 +1603,11 @@ def test_video_transcript_get_request(self): } }, ) - def test_video_transcript_get_request_transcription(self): + def test_video_transcript_get_request_transcription(self) -> None: """Check response for get request with use transcription.""" reload(views) - def inner_get_transcription_choices(): + def inner_get_transcription_choices() -> list: return [("fr", "French"), ("en", "english")] views.get_transcription_choices = inner_get_transcription_choices @@ -1686,7 +1686,7 @@ class VideoAccessTokenTestView(TestCase): "initial_data.json", ] - def setUp(self): + def setUp(self) -> None: """Set up data for test class.""" user = User.objects.create(username="pod", password="pod1234pod") User.objects.create(username="pod2", password="pod1234pod") @@ -1699,7 +1699,7 @@ def setUp(self): ) print(" ---> SetUp of VideoAccessTokenTestView: OK!") - def test_video_access_tokens_get_request(self): + def test_video_access_tokens_get_request(self) -> None: """Check response for get request with default settings.""" # anonyme self.client = Client() @@ -1733,7 +1733,7 @@ def test_video_access_tokens_get_request(self): self.assertEqual(response.status_code, 200) print(" ---> test_video_access_tokens_get_request: OK!") - def test_video_access_tokens_post_request(self): + def test_video_access_tokens_post_request(self) -> None: """Check response for post request.""" video = Video.objects.get(title="Video1") self.user = User.objects.get(username="pod") diff --git a/pod/video/utils.py b/pod/video/utils.py index 5bad521e52..0c538e1481 100644 --- a/pod/video/utils.py +++ b/pod/video/utils.py @@ -103,7 +103,7 @@ def get_headband(channel, theme=None): return result -def change_owner(video_id, new_owner): +def change_owner(video_id, new_owner) -> bool: """Replace current video_id owner by new_owner.""" if video_id is None: return False @@ -117,7 +117,7 @@ def change_owner(video_id, new_owner): return True -def move_video_file(video, new_owner): +def move_video_file(video, new_owner) -> None: """Move video files in new_owner folder.""" # overview and encoding video folder name encod_folder_pattern = "%04d" % video.id @@ -156,7 +156,7 @@ def move_video_file(video, new_owner): video.save() -def get_videos(title, user_id, search=None, limit=12, offset=0): +def get_videos(title, user_id, search=None, limit: int = 12, offset: int = 0) -> JsonResponse: """Return videos filtered by GET parameters 'title' with limit and offset. Args: @@ -198,7 +198,7 @@ def get_videos(title, user_id, search=None, limit=12, offset=0): return JsonResponse(response, safe=False) -def sort_videos_list(videos_list, sort_field, sort_direction=""): +def sort_videos_list(videos_list: list, sort_field: str, sort_direction: str = ""): """Return videos list sorted by sort_field. Sorted by specific column name and ascending or descending direction @@ -263,7 +263,7 @@ def get_video_data(video): } -def get_storage_path_video(instance, filename): +def get_storage_path_video(instance, filename) -> str: """Get the video storage path. Instance needs to implement owner @@ -289,7 +289,7 @@ def get_storage_path_video(instance, filename): ) -def verify_field_length(field, field_name="title", max_length=100) -> list: +def verify_field_length(field, field_name: str = "title", max_length: int = 100) -> list: """Check field length, and return message.""" msg = list() if not field or field == "": diff --git a/pod/video_encode_transcript/models.py b/pod/video_encode_transcript/models.py index 09f1b2de68..31342c3c2c 100644 --- a/pod/video_encode_transcript/models.py +++ b/pod/video_encode_transcript/models.py @@ -96,12 +96,12 @@ class VideoRendition(models.Model): sites = models.ManyToManyField(Site) @property - def height(self): + def height(self) -> int: """The height of the video rendition based on the resolution.""" return int(self.resolution.split("x")[1]) @property - def width(self): + def width(self) -> int: """The width of the video rendition based on the resolution.""" return int(self.resolution.split("x")[0]) @@ -110,13 +110,13 @@ class Meta: verbose_name = _("rendition") verbose_name_plural = _("renditions") - def __str__(self): + def __str__(self) -> str: return "VideoRendition num %s with resolution %s" % ( "%04d" % self.id, self.resolution, ) - def bitrate(self, field_value, field_name, name=None): + def bitrate(self, field_value, field_name, name=None) -> None: """Validate the bitrate field value.""" if name is None: name = field_name @@ -133,13 +133,13 @@ def bitrate(self, field_value, field_name, name=None): "%s %s" % (msg, VideoRendition._meta.get_field(field_name).help_text) ) - def clean_bitrate(self): + def clean_bitrate(self) -> None: """Clean the bitrate-related fields.""" self.bitrate(self.video_bitrate, "video_bitrate", "bitrate video") self.bitrate(self.maxrate, "maxrate") self.bitrate(self.minrate, "minrate") - def clean(self): + def clean(self) -> None: """Clean the fields of the VideoRendition model.""" if self.resolution and "x" not in self.resolution: raise ValidationError(VideoRendition._meta.get_field("resolution").help_text) @@ -155,9 +155,9 @@ def clean(self): @receiver(post_save, sender=VideoRendition) -def default_site_videorendition(sender, instance, created, **kwargs): +def default_site_videorendition(sender, instance, created: bool, **kwargs) -> None: """Add the current site as a default site.""" - if len(instance.sites.all()) == 0: + if instance.sites.count() == 0: instance.sites.add(Site.objects.get_current()) @@ -200,7 +200,7 @@ def sites_all(self): """Property representing all the sites associated with the video.""" return self.video.sites_set.all() - def clean(self): + def clean(self) -> None: """Validate the encoding video model.""" if self.name: if self.name not in dict(ENCODING_CHOICES): @@ -216,7 +216,7 @@ class Meta: verbose_name = _("Encoding video") verbose_name_plural = _("Encoding videos") - def __str__(self): + def __str__(self) -> str: return "EncodingVideo num: %s with resolution %s for video %s in %s" % ( "%04d" % self.id, self.name, @@ -230,16 +230,16 @@ def owner(self): return self.video.owner @property - def height(self): + def height(self) -> int: """Property representing the height of the video rendition.""" return int(self.rendition.resolution.split("x")[1]) @property - def width(self): + def width(self) -> int: """Property representing the width of the video rendition.""" return int(self.rendition.resolution.split("x")[0]) - def delete(self): + def delete(self) -> None: """Delete the encoding video.""" if self.source_file: if os.path.isfile(self.source_file.path): @@ -288,7 +288,7 @@ class Meta: verbose_name = _("Encoding audio") verbose_name_plural = _("Encoding audios") - def clean(self): + def clean(self) -> None: """Validate the encoding audio model.""" if self.name: if self.name not in dict(ENCODING_CHOICES): @@ -299,7 +299,7 @@ def clean(self): EncodingAudio._meta.get_field("encoding_format").help_text ) - def __str__(self): + def __str__(self) -> str: return "EncodingAudio num: %s for video %s in %s" % ( "%04d" % self.id, self.video.id, @@ -311,7 +311,7 @@ def owner(self): """Property representing the owner of the video.""" return self.video.owner - def delete(self): + def delete(self) -> None: """Delete the encoding audio, including the source file if it exists.""" if self.source_file: if os.path.isfile(self.source_file.path): @@ -411,7 +411,7 @@ def sites(self): def sites_all(self): return self.video.sites_set.all() - def clean(self): + def clean(self) -> None: """Validate some PlaylistVideomodels fields.""" if self.name: if self.name not in dict(ENCODING_CHOICES): @@ -425,7 +425,7 @@ def clean(self): code="invalid_encoding", ) - def __str__(self): + def __str__(self) -> str: return "Playlist num: %s for video %s in %s" % ( "%04d" % self.id, self.video.id, @@ -436,7 +436,7 @@ def __str__(self): def owner(self): return self.video.owner - def delete(self): + def delete(self) -> None: if self.source_file: if os.path.isfile(self.source_file.path): os.remove(self.source_file.path) diff --git a/pod/xapi/static/xapi/script-video.js b/pod/xapi/static/xapi/script-video.js index 763d8c4efa..3901c0bc34 100644 --- a/pod/xapi/static/xapi/script-video.js +++ b/pod/xapi/static/xapi/script-video.js @@ -1,3 +1,22 @@ +/** + * Esup-Pod Xapi video scripts. + */ + +// Read-only globals defined in video-script.html +/* +global player +*/ + +// Read-only globals defined in xapi/script.js +/* +global createStatement sendStatement +*/ + +// Read-only globals defined in xapi_video.html +/* +global progress +*/ + const __XAPI_VIDEO_VERBS__ = { initialized: "http://adlnet.gov/expapi/verbs/initialized", played: "https://w3id.org/xapi/video/verbs/played", @@ -44,8 +63,8 @@ var time_paused = 0; var time_seek = 0; player.on("timeupdate", function () { - progress[parseInt(player.currentTime())] = 1; - if (progress.length == parseInt(player.duration())) { + progress[parseInt(player.currentTime(), 10)] = 1; + if (progress.length == parseInt(player.duration(), 10)) { set_completed_statement(); active_statement(); } @@ -123,7 +142,7 @@ player.on("pause", function () { active_statement(); }); -player.on("seeked", function (e) { +player.on("seeked", function () { time_seek = player.currentTime().toFixed(3); action = "seeked"; verb = { @@ -154,7 +173,7 @@ player.on("seeked", function (e) { active_statement(); }); -player.on("ended", function (e) { +player.on("ended", function () { action = "terminated"; verb = { id: __XAPI_VIDEO_VERBS__[action], @@ -188,7 +207,7 @@ player.on("ended", function (e) { .duration() .toFixed(3), "https://w3id.org/xapi/video/extensions/completion-threshold": ( - parseInt(player.duration()) / player.duration() + parseInt(player.duration(), 10) / player.duration() ).toFixed(3), }, registration: registration, @@ -196,28 +215,28 @@ player.on("ended", function (e) { active_statement(); }); -player.on("ratechange", function (e) { +player.on("ratechange", function () { set_interacted_statement(); active_statement(); }); -player.on("fullscreenchange", function (e) { +player.on("fullscreenchange", function () { set_interacted_statement(); active_statement(); }); -player.on("volumechange", function (e) { +player.on("volumechange", function () { set_interacted_statement(); active_statement(); }); -player.on("texttrackchange", function (e) { +player.on("texttrackchange", function () { set_interacted_statement(); active_statement(); }); -player.on("resize", function (e) { +player.on("resize", function () { set_interacted_statement(); active_statement(); }); -player.on("loadedmetadata", function (e) { +player.on("loadedmetadata", function () { action = "initialized"; verb = { id: __XAPI_VIDEO_VERBS__[action], @@ -243,7 +262,7 @@ player.on("loadedmetadata", function (e) { window.navigator.userAgent, "https://w3id.org/xapi/video/extensions/speed": player.playbackRate(), "https://w3id.org/xapi/video/extensions/completion-threshold": ( - parseInt(player.duration()) / player.duration() + parseInt(player.duration(), 10) / player.duration() ).toFixed(3), "https://w3id.org/xapi/video/extensions/session-id": session_id, "https://w3id.org/xapi/video/extensions/length": player @@ -295,7 +314,7 @@ function set_completed_statement() { }, extensions: { "https://w3id.org/xapi/video/extensions/completion-threshold": ( - parseInt(player.duration()) / player.duration() + parseInt(player.duration(), 10) / player.duration() ).toFixed(3), "https://w3id.org/xapi/video/extensions/session-id": session_id, "https://w3id.org/xapi/video/extensions/length": player @@ -344,7 +363,7 @@ function set_interacted_statement() { window.navigator.userAgent, "https://w3id.org/xapi/video/extensions/speed": player.playbackRate(), "https://w3id.org/xapi/video/extensions/completion-threshold": ( - parseInt(player.duration()) / player.duration() + parseInt(player.duration(), 10) / player.duration() ).toFixed(3), "https://w3id.org/xapi/video/extensions/session-id": session_id, "https://w3id.org/xapi/video/extensions/length": player @@ -364,9 +383,9 @@ function set_interacted_statement() { } function get_current_subtitle_lang() { - textTracks = player.textTracks(); - lang = ""; - for (var i = 0; i < textTracks.length; i++) { + let textTracks = player.textTracks(); + let lang = ""; + for (let i = 0; i < textTracks.length; i++) { if (textTracks[i].kind == "subtitles" && textTracks[i].mode == "showing") { lang = textTracks[i].language; } @@ -375,12 +394,12 @@ function get_current_subtitle_lang() { } function get_current_quality() { - qualitys = player.qualityLevels(); + let qualitys = player.qualityLevels(); if (qualitys.length > 0) return qualitys[qualitys.selectedIndex]; } function active_statement() { timestamp = new Date().toISOString(); - stmt = createStatement(); + let stmt = createStatement(); sendStatement(stmt); } diff --git a/pod/xapi/static/xapi/script.js b/pod/xapi/static/xapi/script.js index 5e1c2d259f..09bda1ff79 100644 --- a/pod/xapi/static/xapi/script.js +++ b/pod/xapi/static/xapi/script.js @@ -1,3 +1,6 @@ +/** + * Esup-Pod Xapi scripts + */ let result, verb, context, diff --git a/scripts/bbb-pod-live/bbb-pod-live.php b/scripts/bbb-pod-live/bbb-pod-live.php index 841d2332c3..49ef168c8a 100644 --- a/scripts/bbb-pod-live/bbb-pod-live.php +++ b/scripts/bbb-pod-live/bbb-pod-live.php @@ -1,230 +1,330 @@ getMessage(); } -// Envoi d'un message à l'administrateur en cas d'erreur de script -if ($GLOBALS["txtErrorInScript"] != "") { +// Envoi d'un message à l'administrateur en cas d'erreur de script. +if ($GLOBALS["txtErrorInScript"] !== "") { sendEmail("[BBB-POD-LIVE] Erreur rencontrée", $GLOBALS["txtErrorInScript"]); } -/********** Fin de la phase principale**********/ +// Fin de la phase principale. + /** - * Procédure de création et de configuration initile des différents plugin BigBlueButton-liveStreaming. - * Un répertoire, par nombre de directs gérés par ce serveur (cf. NUMBER_LIVES), sera créé sous la forme bbb-live-streaming+incrémental. - * Le fichier compose.yml sera copié depuis le répertoire courant (fichier docker-compose.default.yml). + * Procédure de création et de configuration initiale + * des différents plugin BigBlueButton-liveStreaming. + * Un répertoire, par nombre de directs gérés par ce serveur (cf. NUMBER_LIVES), + * sera créé sous la forme bbb-live-streaming+incrémental. + * Le fichier compose.yml sera copié depuis le répertoire courant + * (fichier docker-compose.default.yml). */ -function configureInitialBigBlueButtonLiveStreaming() +function configureBBBLiveStreaming() { - writeLog("----- Configuration des plugins nécessaires : configureBigBlueButtonLiveStreaming()-----", "DEBUG"); - // Création des répertoires et des fichiers compose pour le plugin BigBlueButton-liveStreaming + writeLog( + "----- Configuration des plugins nécessaires : configureBBBLiveStreaming()-----", + "DEBUG" + ); + + /* + Création des répertoires et des fichiers compose + pour le plugin BigBlueButton-liveStreaming. + */ + for ($i = 1; $i <= NUMBER_LIVES; $i++) { - // Définition du répertoire - $directoryLiveStreaming = checkEndSlash(PHYSICAL_BASE_ROOT) . "bbb-live-streaming$i/"; - writeLog("Vérification pour le direct $i : $directoryLiveStreaming", "DEBUG"); - // Définition du fichier compose.yml dans ce répertoire - $fichierCompose = $directoryLiveStreaming . "docker-compose.yml"; - // Création du répertoire et du fichier la 1° fois - if (! file_exists($fichierCompose)) { - // Création du répertoire - writeLog(" + Création du répertoire $directoryLiveStreaming", "DEBUG"); - @mkdir("$directoryLiveStreaming", 0755); - // Téléchargement du fichier compose depuis Github - writeLog(" + Copie du fichier docker-compose.default.yml du répertoire courant", "DEBUG"); + // Définition du répertoire. + $dirLiveStreaming = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming$i/"; + writeLog( + "Vérification pour le direct $i : $dirLiveStreaming", + "DEBUG" + ); + // Définition du fichier compose.yml dans ce répertoire. + $fichierCompose = $dirLiveStreaming."docker-compose.yml"; + // Création du répertoire et du fichier la 1° fois. + if (file_exists($fichierCompose) === false) { + // Création du répertoire. + writeLog( + " + Création du répertoire $dirLiveStreaming", + "DEBUG" + ); + @mkdir("$dirLiveStreaming", 0755); + // Téléchargement du fichier compose depuis Github. + writeLog( + " + Copie du fichier docker-compose.default.yml du répertoire courant", + "DEBUG" + ); $cmdCp = "cp ./docker-compose.default.yml $fichierCompose"; - exec("$cmdCp 2>&1", $aRetourVerificationCp, $sRetourVerificationCp); - if ($sRetourVerificationCp == 0) { - writeLog(" + Copie du fichier $fichierCompose réalisée", "DEBUG"); + exec("$cmdCp 2>&1", $aVerifCp, $sVerifCp); + if ($sVerifCp === 0) { + writeLog( + " + Copie du fichier $fichierCompose réalisée", + "DEBUG" + ); } else { - writeLog(" - Commande '$cmdCp' : $aRetourVerificationCp[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " - Commande '$cmdCp' : $aVerifCp[0]", + "ERROR", + __FILE__, __LINE__ + ); } } } + } +// end configureBBBLiveStreaming() + /** - * Procédure permettant de démarrer des directs, si des usagers en ont fait la demande dans Pod. + * Procédure permettant de démarrer des directs, + * si des usagers en ont fait la demande dans Pod. * Pour cela, on utilise l'API Rest de Pod. - * Cette procédure permet d'identifier si un slot est disponible pour être utilisé pour lancer un direct. + * Cette procédure permet d'identifier si un slot est disponible + * pour être utilisé pour lancer un direct. */ function startLives() { - writeLog("-----Démarrage des directs : startLives()-----", "DEBUG"); + writeLog( + "-----Démarrage des directs : startLives()-----", + "DEBUG" + ); - // Recherche si des lives sont en cours + // Recherche si des lives sont en cours. $cmdStatus1 = "curl --silent -H 'Content-Type: application/json' "; - $cmdStatus1 .= "-H 'Authorization: Token " . POD_TOKEN . "' "; - $cmdStatus1 .= "-X GET " . checkEndWithoutSlash(POD_URL). "/rest/bbb_livestream/?status=1"; + $cmdStatus1 .= "-H 'Authorization: Token ".POD_TOKEN."' "; + $cmdStatus1 .= "-X GET ".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/?status=1"; - $verificationStatus1 = exec("$cmdStatus1 2>&1", $aRetourVerificationStatus1, $sRetourVerificationStatus1); + exec("$cmdStatus1 2>&1", $aVerifStatus1, $sVerifStatus1); - writeLog("Recherche si des lives sont en cours", "DEBUG"); + writeLog( + "Recherche si des lives sont en cours", + "DEBUG" + ); - // En cas d'erreur, le code de retour est différent de 0 - if ($sRetourVerificationStatus1 == 0) { - writeLog(" + Commande '$cmdStatus1' : $aRetourVerificationStatus1[0]", "DEBUG"); + // En cas d'erreur, le code de retour est différent de 0. + if ($sVerifStatus1 === 0) { + writeLog( + " + Commande '$cmdStatus1' : $aVerifStatus1[0]", + "DEBUG" + ); - $oListeSessions = json_decode($aRetourVerificationStatus1[0]); - // Recherche des lives existants en cours, sauvegardés dans Pod + $oListeSessions = json_decode($aVerifStatus1[0]); + // Recherche des lives existants en cours, sauvegardés dans Pod. for ($i = 0; $i < $oListeSessions->count; $i++) { - // Identifiant du live dans Pod + // Identifiant du live dans Pod. $idLive = $oListeSessions->results[$i]->id; - // Dans Pod, l'information est sauvegardé sous la forme NUMERO_SERVEUR/NUMERO_REPERTOIRE_bbb_live_streaming + // Dans Pod, l'information est sauvegardé sous la forme + // NUMERO_SERVEUR/NUMERO_REPERTOIRE_bbb_live_streaming. $server = $oListeSessions->results[$i]->server; // Le live est il déjà en cours sur un des serveurs BBB-POD-LIVE ? $status = $oListeSessions->results[$i]->status; - // Utilisateur ayant lancé ce live + // Utilisateur ayant lancé ce live. $user = $oListeSessions->results[$i]->user; - // Prise en compte seulement des lives en cours de ce serveur - if (($status == 1) && (strpos("$server", SERVER_NUMBER . "/") !== false)) { - // Sauvegarde du NUMERO_REPERTOIRE_bbb_live_streaming - $processInProgress = str_replace(SERVER_NUMBER . "/", "", $server); - // Utilisation d'une classe standard + // Prise en compte seulement des lives en cours de ce serveur. + if (($status === 1) + && (strpos("$server", SERVER_NUMBER."/") !== false) + ) { + // Sauvegarde du NUMERO_REPERTOIRE_bbb_live_streaming. + $processInProgress = str_replace(SERVER_NUMBER."/", "", $server); + // Utilisation d'une classe standard. $liveInProgress = new stdClass(); $liveInProgress->id = $idLive; $liveInProgress->idBbbLiveStreaming = $processInProgress; - // Ajout de cet objet au tableau des lives en cours sur ce serveur + // Ajout de cet objet au tableau des lives en cours sur ce serveur. $GLOBALS["livesInProgressOnThisServer"][] = $liveInProgress; - writeLog(" => Le live $idLive de $user est toujours en cours sur le serveur/bbb_live_streaming : $server.", "DEBUG"); + writeLog( + " => Le live $idLive de $user est toujours en cours sur le serveur/bbb_live_streaming : $server.", + "DEBUG" + ); } } } else { - writeLog(" + Commande '$cmdStatus1' : $sRetourVerificationStatus1[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdStatus1' : $sVerifStatus1[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Recherche si des utilisateurs ont lancé des lives depuis Pod + // Recherche si des utilisateurs ont lancé des lives depuis Pod. $cmdStatus0 = "curl --silent -H 'Content-Type: application/json' "; - $cmdStatus0 .= "-H 'Authorization: Token " . POD_TOKEN . "' "; - $cmdStatus0 .= "-X GET " . checkEndWithoutSlash(POD_URL) . "/rest/bbb_livestream/?status=0"; + $cmdStatus0 .= "-H 'Authorization: Token ".POD_TOKEN."' "; + $cmdStatus0 .= "-X GET ".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/?status=0"; - $verificationStatus0 = exec("$cmdStatus0 2>&1", $aRetourVerificationStatus0, $sRetourVerificationStatus0); + exec("$cmdStatus0 2>&1", $aVerifStatus0, $sVerifStatus0); - writeLog("Recherche si des utilisateurs ont lancé des lives depuis Pod", "DEBUG"); + writeLog( + "Recherche si des utilisateurs ont lancé des lives depuis Pod", + "DEBUG" + ); - // En cas d'erreur, le code de retour est différent de 0 - if ($sRetourVerificationStatus0 == 0) { - writeLog(" + Commande '$cmdStatus0' : $aRetourVerificationStatus0[0]", "DEBUG"); + // En cas d'erreur, le code de retour est différent de 0. + if ($sVerifStatus0 === 0) { + writeLog( + " + Commande '$cmdStatus0' : $aVerifStatus0[0]", + "DEBUG" + ); - $oListeSessions = json_decode($aRetourVerificationStatus0[0]); - // Recherche des nouvelles demandes de lives, sauvegardées dans Pod + $oListeSessions = json_decode($aVerifStatus0[0]); + // Recherche des nouvelles demandes de lives, sauvegardées dans Pod. for ($i = 0; $i < $oListeSessions->count; $i++) { - // Identifiant du live BBB dans Pod + // Identifiant du live BBB dans Pod. $idLive = $oListeSessions->results[$i]->id; - // Adresse de la session dans Pod + // Adresse de la session dans Pod. $urlMeeting = $oListeSessions->results[$i]->meeting; - // Nom du serveur/processus déjà en charge de ce live + // Nom du serveur/processus déjà en charge de ce live. $server = $oListeSessions->results[$i]->server; // Le live est il déjà en cours sur un des serveurs BBB-POD-LIVE ? $status = $oListeSessions->results[$i]->status; - // Utilisateur ayant lancé ce live + // Utilisateur ayant lancé ce live. $user = $oListeSessions->results[$i]->user; - // Identifiant du répertoire bbb-live-streaming qui s'occupera de réaliser le live, si disponible + /* + Identifiant du répertoire bbb-live-streaming qui s'occupera de + réaliser le live, si disponible. + */ + $idBbbLiveStreaming = 0; - // Recherche si ce serveur peut encore lancer un direct + // Recherche si ce serveur peut encore lancer un direct. for ($j = 1; $j <= NUMBER_LIVES; $j++) { - // Variable de travail + // Variable de travail. $idBbbLiveStreamingUsed = false; foreach ($GLOBALS["livesInProgressOnThisServer"] as $ligneLiveInProgressOnThisServer) { - // Cet idBbbLiveStreaming est déjà utilisé - if ($ligneLiveInProgressOnThisServer->idBbbLiveStreaming == $j) { + // Cet idBbbLiveStreaming est déjà utilisé. + if ($ligneLiveInProgressOnThisServer->idBbbLiveStreaming === $j) { $idBbbLiveStreamingUsed = true; } } - // Le slot idBbbLiveStreaming est non utilisé - if (! $idBbbLiveStreamingUsed) { - // Un slot est disponible + // Le slot idBbbLiveStreaming est non utilisé. + if ($idBbbLiveStreamingUsed === false) { + // Un slot est disponible. $idBbbLiveStreaming = $j; - // Ajout de l'information aux lives en cours sur ce serveur + // Ajout de l'information aux lives en cours sur ce serveur. $liveInProgress2 = new stdClass(); $liveInProgress2->id = $idLive; $liveInProgress2->idBbbLiveStreaming = $idBbbLiveStreaming; @@ -233,467 +333,755 @@ function startLives() } } // Un slot est disponible sur ce serveur pour réaliser un live ? - if ($idBbbLiveStreaming == 0) { - writeLog(" => Impossible de lancer le live $idLive de $user sur ce serveur : il y a déjà " . NUMBER_LIVES . " directs qui sont gérés par ce serveur.", "INFO"); + if ($idBbbLiveStreaming === 0) { + writeLog( + " => Impossible de lancer le live $idLive de $user sur ce serveur : il y a déjà ".NUMBER_LIVES." directs qui sont gérés par ce serveur.", + "INFO" + ); } else { - writeLog(" => Lancement du live $idLive de $user, via bbb-live-streaming$idBbbLiveStreaming", "INFO"); + writeLog( + " => Lancement du live $idLive de $user, via bbb-live-streaming$idBbbLiveStreaming", + "INFO" + ); configureAndStartLive($idLive, $urlMeeting, $idBbbLiveStreaming); } } } else { - writeLog(" + Commande '$cmdStatus0' : $sRetourVerificationStatus0[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdStatus0' : $sVerifStatus0[0]", + "ERROR", __FILE__, __LINE__ + ); } + } /** - * Procédure permettant de configurer, puis de lancer, le plugin nécessaire au démarrage d'un direct. - * Cette procédure créé également le diffuseur nécessaire à l'affichage du live dans Pod. - * @param string $idLive - Identifiant du live BBB de Pod à démarrer (cf. table bbb_meeting) - * @param string $urlMeeting - URL de la session BBB de Pod à démarrer (cf. table bbb_livestream) - * @param string $idBbbLiveStreaming - Identifiant du répertoire bbb-live-streaming qui va être utilisé pour lancer ce direct + * Procédure permettant de configurer, puis de lancer, + * le plugin nécessaire au démarrage d'un direct. + * Cette procédure créé également le diffuseur nécessaire + * à l'affichage du live dans Pod. + * + * @param string $idLive - Identifiant du live BBB de Pod à démarrer + * (cf. table bbb_meeting) + * @param string $urlMeeting - URL de la session BBB de Pod à démarrer + * (cf. table bbb_livestream) + * @param string $idBbbLiveStreaming - Identifiant du répertoire bbb-live-streaming + * qui va être utilisé pour lancer ce direct */ function configureAndStartLive($idLive, $urlMeeting, $idBbbLiveStreaming) { - writeLog("-----Configuration et démarrage du direct : configureAndStartLive($idLive, '$urlMeeting', $idBbbLiveStreaming)-----", "DEBUG"); + writeLog( + "-----Configuration et démarrage du direct : configureAndStartLive($idLive, '$urlMeeting', $idBbbLiveStreaming)-----", + "DEBUG" + ); $cmd = "curl --silent -H 'Content-Type: application/json' "; - $cmd .= "-H 'Authorization: Token " . POD_TOKEN . "' "; + $cmd .= "-H 'Authorization: Token ".POD_TOKEN."' "; $cmd .= "-X GET $urlMeeting"; - $verification = exec("$cmd 2>&1", $aRetourVerification, $sRetourVerification); - - writeLog("Récupération de l'objet meeting ($urlMeeting) depuis Pod", "DEBUG"); - - if ($sRetourVerification == 0) { - writeLog(" + Commande '$cmd' : $aRetourVerification[0]", "DEBUG"); - - // Récupération de l'objet meeting - $oMeeting = json_decode($aRetourVerification[0]); - // Nom de la session, sans caractères problématiques ni espaces, et la chaîne bbb- en premier pour éviter toute confusion avec un diffuseur existant - $nameMeeting = "bbb-" . formatString($oMeeting->meeting_name); - // Nom de la session, sans caractères problématiques avec espaces, et la chaîne (BBB) en premier pour éviter toute confusion avec un diffuseur existant - $nameMeetingToDisplay = "[BBB] " . formatStringToDisplay($oMeeting->meeting_name); - // Id de la session + exec("$cmd 2>&1", $aVerif, $sVerif); + + writeLog( + "Récupération de l'objet meeting ($urlMeeting) depuis Pod", + "DEBUG" + ); + + if ($sVerif === 0) { + writeLog( + " + Commande '$cmd' : $aVerif[0]", + "DEBUG" + ); + + // Récupération de l'objet meeting. + $oMeeting = json_decode($aVerif[0]); + /* + Nom de la session, sans caractères problématiques ni espaces, et la + chaîne bbb- en premier pour éviter toute confusion + avec un diffuseur existant. + */ + + $nameMeeting = "bbb-".formatString($oMeeting->meeting_name); + /* + Nom de la session, sans caractères problématiques avec espaces, + et la chaîne [BBB] en premier pour éviter toute confusion + avec un diffuseur existant. + */ + + $nameMeetingToDisplay = "[BBB] ".formatStringToDisplay($oMeeting->meeting_name); + // Id de la session. $idMeeting = $oMeeting->meeting_id; + /* + Récupération des informations concernant + les options saisies par l'utilisateur. + */ - // Récupération des informations concernant les options saisies par l'utilisateur $cmdOptions = "curl --silent -H 'Content-Type: application/json' "; - $cmdOptions .= "-H 'Authorization: Token " . POD_TOKEN . "' "; - $cmdOptions .= "-X GET " . checkEndWithoutSlash(POD_URL) . "/rest/bbb_livestream/$idLive/"; - $verificationOptions = exec("$cmdOptions 2>&1", $aRetourVerificationOptions, $sRetourVerificationOptions); + $cmdOptions .= "-H 'Authorization: Token ".POD_TOKEN."' "; + $cmdOptions .= "-X GET ".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/$idLive/"; + exec("$cmdOptions 2>&1", $aVerifOptions, $sVerifOptions); - writeLog("Récupération des options de l'objet bbb_livestream (/rest/bbb_livestream/$idLive/) depuis Pod", "DEBUG"); + writeLog( + "Récupération des options de l'objet bbb_livestream (/rest/bbb_livestream/$idLive/) depuis Pod", + "DEBUG" + ); $isRestricted = "false"; $enableChat = "false"; $showChat = "true"; $downloadMeeting = "false"; - if ($sRetourVerificationOptions == 0) { - // Récupération de l'objet live - $oLive = json_decode($aRetourVerificationOptions[0]); - // Accès restreint - if ($oLive->is_restricted == 1) { + if ($sVerifOptions === 0) { + // Récupération de l'objet live. + $oLive = json_decode($aVerifOptions[0]); + // Accès restreint. + if ($oLive->is_restricted === 1) { $isRestricted = "true"; } else { $isRestricted = "false"; } - // Utilisation du chat - if ($oLive->enable_chat == 1) { + // Utilisation du chat. + if ($oLive->enable_chat === 1) { $enableChat = "true"; } else { $enableChat = "false"; } - // Affichage du chat dans la vidéo - if ($oLive->show_chat == 1) { + // Affichage du chat dans la vidéo. + if ($oLive->show_chat === 1) { $showChat = "true"; } else { $showChat = "false"; } - // Téléchargement de la vidéo en fin de live - if ($oLive->download_meeting == 1) { + // Téléchargement de la vidéo en fin de live. + if ($oLive->download_meeting === 1) { $downloadMeeting = "true"; } else { $downloadMeeting = "false"; } } else { - writeLog(" + Commande '$cmdOptions' : $sRetourVerificationOptions[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdOptions' : $sVerifOptions[0]", + "ERROR", __FILE__, __LINE__ + ); } - /* Modification de la configuration du docker-compose.yml */ - writeLog(" + Modification de la configuration du docker-compose.yml", "DEBUG"); - $dockerFile = checkEndSlash(PHYSICAL_BASE_ROOT) . "bbb-live-streaming" . $idBbbLiveStreaming . "/docker-compose.yml"; - // Configuration du nom du container (container_name) - $nameContainer = "liveStreaming" . $idBbbLiveStreaming; + // Modification de la configuration du docker-compose.yml. + writeLog( + " + Modification de la configuration du docker-compose.yml", + "DEBUG" + ); + $dockerFile = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$idBbbLiveStreaming."/docker-compose.yml"; + // Configuration du nom du container (container_name). + $nameContainer = "liveStreaming".$idBbbLiveStreaming; $cmdSed0 = "sed -i \"s/^.*container_name:.*/ container_name: $nameContainer/\" $dockerFile"; - exec("$cmdSed0 2>&1", $aRetourVerificationSed0, $sRetourVerificationSed0); - if ($sRetourVerificationSed0 != 0) { - writeLog(" - Commande '$cmdSed0' : $aRetourVerificationSed0[0]", "ERROR", __FILE__, __LINE__); + exec("$cmdSed0 2>&1", $aVerifSed0, $sVerifSed0); + if ($sVerifSed0 !== 0) { + writeLog( + " - Commande '$cmdSed0' : $aVerifSed0[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Configuration du port utilisé par le host (ligne sous ports:), de la forme - "6379:6379" pour 1°, - "6380:6379" pour le 2°.... - $port = 6378 + $idBbbLiveStreaming; + /* + Configuration du port utilisé par le host (ligne sous ports:), + de la forme - "6379:6379" pour 1°, - "6380:6379" pour le 2°... + */ + + $port = (6378 + $idBbbLiveStreaming); $cmdSed01 = "sed -i \"s/^.*:6379:.*/ - \"$port:6379\"/\" $dockerFile"; - exec("$cmdSed01 2>&1", $aRetourVerificationSed01, $sRetourVerificationSed01); - if ($sRetourVerificationSed01 != 0) { - writeLog(" - Commande '$cmdSed01' : $aRetourVerificationSed01[0]", "ERROR", __FILE__, __LINE__); + exec("$cmdSed01 2>&1", $aVerifSed01, $sVerifSed01); + if ($sVerifSed01 !== 0) { + writeLog( + " - Commande '$cmdSed01' : $aVerifSed01[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Configuration du serveur BBB/Scalelite (BBB_URL) - // Gestion des caractères / de BBB_URL pour être utilisé via sed + /* + Configuration du serveur BBB/Scalelite (BBB_URL) + Gestion des caractères / de BBB_URL pour être utilisé via sed. + */ + $bbbURL = str_replace("/", "\/", checkEndWithoutSlash(BBB_URL)); $cmdSed1 = "sed -i \"s/^.*BBB_URL=.*/ - BBB_URL=$bbbURL/\" $dockerFile"; - exec("$cmdSed1 2>&1", $aRetourVerificationSed1, $sRetourVerificationSed1); - if ($sRetourVerificationSed1 != 0) { - writeLog(" - Commande '$cmdSed1' : $aRetourVerificationSed1[0]", "ERROR", __FILE__, __LINE__); + exec("$cmdSed1 2>&1", $aVerifSed1, $sVerifSed1); + if ($sVerifSed1 !== 0) { + writeLog( + " - Commande '$cmdSed1' : $aVerifSed1[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Configuration de la clé secrète (BBB_SECRET) + // Configuration de la clé secrète (BBB_SECRET). $cmdSed2 = "sed -i \"s/^.*BBB_SECRET=.*/ - BBB_SECRET=".BBB_SECRET."/\" $dockerFile"; - exec("$cmdSed2 2>&1", $aRetourVerificationSed2, $sRetourVerificationSed2); - if ($sRetourVerificationSed2 != 0) { - writeLog(" - Commande '$cmdSed2' : $aRetourVerificationSed2[0]", "ERROR", __FILE__, __LINE__); + exec("$cmdSed2 2>&1", $aVerifSed2, $sVerifSed2); + if ($sVerifSed2 !== 0) { + writeLog( + " - Commande '$cmdSed2' : $aVerifSed2[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Configuration de la timezone (TZ) + // Configuration de la timezone (TZ). $cmdSed3 = "sed -i \"s/^.*TZ=.*/ - TZ=Europe\/Paris/\" $dockerFile"; - exec("$cmdSed3 2>&1", $aRetourVerificationSed3, $sRetourVerificationSed3); - if ($sRetourVerificationSed3 != 0) { - writeLog(" - Commande '$cmdSed3' : $aRetourVerificationSed3[0]", "ERROR", __FILE__, __LINE__); + exec("$cmdSed3 2>&1", $aVerifSed3, $sVerifSed3); + if ($sVerifSed3 !== 0) { + writeLog( + " - Commande '$cmdSed3' : $aVerifSed3[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Configuration de la résolution (BBB_RESOLUTION) + // Configuration de la résolution (BBB_RESOLUTION). $cmdSed4 = "sed -i \"s/^.*BBB_RESOLUTION=.*/ - BBB_RESOLUTION=".BBB_RESOLUTION."/\" $dockerFile"; - exec("$cmdSed4 2>&1", $aRetourVerificationSed4, $sRetourVerificationSed4); - if ($sRetourVerificationSed4 != 0) { - writeLog(" - Commande '$cmdSed4' : $aRetourVerificationSed4[0]", "ERROR", __FILE__, __LINE__); + exec("$cmdSed4 2>&1", $aVerifSed4, $sVerifSed4); + if ($sVerifSed4 !== 0) { + writeLog( + " - Commande '$cmdSed4' : $aVerifSed4[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Configuration du bitrate de la vidéo (FFMPEG_STREAM_VIDEO_BITRATE) + // Configuration du bitrate de la vidéo (FFMPEG_STREAM_VIDEO_BITRATE). $cmdSed5 = "sed -i \"s/^.*FFMPEG_STREAM_VIDEO_BITRATE=.*/ - FFMPEG_STREAM_VIDEO_BITRATE=".FFMPEG_STREAM_VIDEO_BITRATE."/\" $dockerFile"; - exec("$cmdSed5 2>&1", $aRetourVerificationSed5, $sRetourVerificationSed5); - if ($sRetourVerificationSed5 != 0) { - writeLog(" - Commande '$cmdSed5' : $aRetourVerificationSed5[0]", "ERROR", __FILE__, __LINE__); + exec("$cmdSed5 2>&1", $aVerifSed5, $sVerifSed5); + if ($sVerifSed5 !== 0) { + writeLog( + " - Commande '$cmdSed5' : $aVerifSed5[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Configuration du nombre de threads (FFMPEG_STREAM_THREADS) + // Configuration du nombre de threads (FFMPEG_STREAM_THREADS). $cmdSed6 = "sed -i \"s/^.*FFMPEG_STREAM_THREADS=.*/ - FFMPEG_STREAM_THREADS=".FFMPEG_STREAM_THREADS."/\" $dockerFile"; - exec("$cmdSed6 2>&1", $aRetourVerificationSed6, $sRetourVerificationSed6); - if ($sRetourVerificationSed6 != 0) { - writeLog(" - Commande '$cmdSed6' : $aRetourVerificationSed6[0]", "ERROR", __FILE__, __LINE__); + exec("$cmdSed6 2>&1", $aVerifSed6, $sVerifSed6); + if ($sVerifSed6 !== 0) { + writeLog( + " - Commande '$cmdSed6' : $aVerifSed6[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant l'id de la session à streamer (BBB_MEETING_ID) - $cmdSed7 = "sed -i \"s/^.*BBB_MEETING_ID=.*/ - BBB_MEETING_ID=$idMeeting/\" " . $dockerFile; - exec("$cmdSed7 2>&1", $aRetourVerificationSed7, $sRetourVerificationSed7); - if ($sRetourVerificationSed7 != 0) { - writeLog(" - Commande '$cmdSed7' : $aRetourVerificationSed7[0]", "ERROR", __FILE__, __LINE__); + /* + Modification de la ligne concernant l'id + de la session à streamer (BBB_MEETING_ID). + */ + + $cmdSed7 = "sed -i \"s/^.*BBB_MEETING_ID=.*/ - BBB_MEETING_ID=$idMeeting/\" ".$dockerFile; + exec("$cmdSed7 2>&1", $aVerifSed7, $sVerifSed7); + if ($sVerifSed7 !== 0) { + writeLog( + " - Commande '$cmdSed7' : $aVerifSed7[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant le flux RTMP (BBB_STREAM_URL) - // Gestion des caractères / du serveur RTMP pour être utilisé via sed + // Modification de la ligne concernant le flux RTMP (BBB_STREAM_URL). + // Gestion des caractères / du serveur RTMP pour être utilisé via sed. $rtmpServer = str_replace("/", "\/", checkEndSlash(BBB_STREAM_URL)); - $cmdSed8 = "sed -i \"s/^.*BBB_STREAM_URL=.*/ - BBB_STREAM_URL=" . $rtmpServer . "$nameMeeting/\" " . $dockerFile; - exec("$cmdSed8 2>&1", $aRetourVerificationSed8, $sRetourVerificationSed8); - if ($sRetourVerificationSed8 != 0) { - writeLog(" - Commande '$cmdSed8' : $aRetourVerificationSed8[0]", "ERROR", __FILE__, __LINE__); + $cmdSed8 = "sed -i \"s/^.*BBB_STREAM_URL=.*/ - BBB_STREAM_URL=".$rtmpServer."$nameMeeting/\" ".$dockerFile; + exec("$cmdSed8 2>&1", $aVerifSed8, $sVerifSed8); + if ($sVerifSed8 !== 0) { + writeLog( + " - Commande '$cmdSed8' : $aVerifSed8[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant l'utilisation de chat (BBB_ENABLE_CHAT) - $cmdSed9 = "sed -i \"s/^.*BBB_ENABLE_CHAT=.*/ - BBB_ENABLE_CHAT=$enableChat/\" " . $dockerFile; - exec("$cmdSed9 2>&1", $aRetourVerificationSed9, $sRetourVerificationSed9); - if ($sRetourVerificationSed9 != 0) { - writeLog(" - Commande '$cmdSed9' : $aRetourVerificationSed9[0]", "ERROR", __FILE__, __LINE__); + /* + Modification de la ligne concernant + l'utilisation de chat (BBB_ENABLE_CHAT). + */ + + $cmdSed9 = "sed -i \"s/^.*BBB_ENABLE_CHAT=.*/ - BBB_ENABLE_CHAT=$enableChat/\" ".$dockerFile; + exec("$cmdSed9 2>&1", $aVerifSed9, $sVerifSed9); + if ($sVerifSed9 !== 0) { + writeLog( + " - Commande '$cmdSed9' : $aVerifSed9[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant l'affichage du chat dans la vidéo (BBB_SHOW_CHAT) - $cmdSed10 = "sed -i \"s/^.*BBB_SHOW_CHAT=.*/ - BBB_SHOW_CHAT=$showChat/\" " . $dockerFile; - exec("$cmdSed10 2>&1", $aRetourVerificationSed10, $sRetourVerificationSed10); - if ($sRetourVerificationSed10 != 0) { - writeLog(" - Commande '$cmdSed10' : $aRetourVerificationSed10[0]", "ERROR", __FILE__, __LINE__); + /* + Modification de la ligne concernant l'affichage + du chat dans la vidéo (BBB_SHOW_CHAT). + */ + + $cmdSed10 = "sed -i \"s/^.*BBB_SHOW_CHAT=.*/ - BBB_SHOW_CHAT=$showChat/\" ".$dockerFile; + exec("$cmdSed10 2>&1", $aVerifSed10, $sVerifSed10); + if ($sVerifSed10 !== 0) { + writeLog( + " - Commande '$cmdSed10' : $aVerifSed10[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant l'enregistrement de la vidéo du live (BBB_DOWNLOAD_MEETING) - $cmdSed11 = "sed -i \"s/^.*BBB_DOWNLOAD_MEETING=.*/ - BBB_DOWNLOAD_MEETING=$downloadMeeting/\" " . $dockerFile; - exec("$cmdSed11 2>&1", $aRetourVerificationSed11, $sRetourVerificationSed11); - if ($sRetourVerificationSed11 != 0) { - writeLog(" - Commande '$cmdSed11' : $aRetourVerificationSed11[0]", "ERROR", __FILE__, __LINE__); + /* + Modification de la ligne concernant l'enregistrement + de la vidéo du live (BBB_DOWNLOAD_MEETING). + */ + + $cmdSed11 = "sed -i \"s/^.*BBB_DOWNLOAD_MEETING=.*/ - BBB_DOWNLOAD_MEETING=$downloadMeeting/\" ".$dockerFile; + exec("$cmdSed11 2>&1", $aVerifSed11, $sVerifSed11); + if ($sVerifSed11 !== 0) { + writeLog( + " - Commande '$cmdSed11' : $aVerifSed11[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant le titre de la session (BBB_MEETING_TITLE) - $cmdSed12 = "sed -i \"s/^.*BBB_MEETING_TITLE=.*/ - BBB_MEETING_TITLE=$nameMeetingToDisplay/\" " . $dockerFile; - exec("$cmdSed12 2>&1", $aRetourVerificationSed12, $sRetourVerificationSed12); - if ($sRetourVerificationSed12 != 0) { - writeLog(" - Commande '$cmdSed12' : $aRetourVerificationSed12[0]", "ERROR", __FILE__, __LINE__); + /* + Modification de la ligne concernant le titre + de la session (BBB_MEETING_TITLE). + */ + + $cmdSed12 = "sed -i \"s/^.*BBB_MEETING_TITLE=.*/ - BBB_MEETING_TITLE=$nameMeetingToDisplay/\" ".$dockerFile; + exec("$cmdSed12 2>&1", $aVerifSed12, $sVerifSed12); + if ($sVerifSed12 !== 0) { + writeLog( + " - Commande '$cmdSed12' : $aVerifSed12[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant le mot de passe d'un participant de la session (BBB_ATTENDEE_PASSWORD) - $cmdSed13 = "sed -i \"s/^.*BBB_ATTENDEE_PASSWORD=.*/ - BBB_ATTENDEE_PASSWORD=".BBB_ATTENDEE_PASSWORD."/\" " . $dockerFile; - exec("$cmdSed13 2>&1", $aRetourVerificationSed13, $sRetourVerificationSed13); - if ($sRetourVerificationSed13 != 0) { - writeLog(" - Commande '$cmdSed13' : $aRetourVerificationSed13[0]", "ERROR", __FILE__, __LINE__); + /* + Modification de la ligne concernant le mot de passe + d'un participant de la session (BBB_ATTENDEE_PASSWORD). + */ + + $cmdSed13 = "sed -i \"s/^.*BBB_ATTENDEE_PASSWORD=.*/ - BBB_ATTENDEE_PASSWORD=".BBB_ATTENDEE_PASSWORD."/\" ".$dockerFile; + exec("$cmdSed13 2>&1", $aVerifSed13, $sVerifSed13); + if ($sVerifSed13 !== 0) { + writeLog( + " - Commande '$cmdSed13' : $aVerifSed13[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant le mot de passe d'un modérateur de la session (BBB_MODERATOR_PASSWORD) - $cmdSed14 = "sed -i \"s/^.*BBB_MODERATOR_PASSWORD=.*/ - BBB_MODERATOR_PASSWORD=".BBB_MODERATOR_PASSWORD."/\" " . $dockerFile; - exec("$cmdSed14 2>&1", $aRetourVerificationSed14, $sRetourVerificationSed14); - if ($sRetourVerificationSed14 != 0) { - writeLog(" - Commande '$cmdSed14' : $aRetourVerificationSed14[0]", "ERROR", __FILE__, __LINE__); + /* + Modification de la ligne concernant le mot de passe + d'un modérateur de la session (BBB_MODERATOR_PASSWORD). + */ + + $cmdSed14 = "sed -i \"s/^.*BBB_MODERATOR_PASSWORD=.*/ - BBB_MODERATOR_PASSWORD=".BBB_MODERATOR_PASSWORD."/\" ".$dockerFile; + exec("$cmdSed14 2>&1", $aVerifSed14, $sVerifSed14); + if ($sVerifSed14 !== 0) { + writeLog( + " - Commande '$cmdSed14' : $aVerifSed14[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant le channel pour REDIS, encas d'utilisation du chat (BBB_REDIS_CHANNEL) + // Modification de la ligne concernant le channel pour REDIS, + // en cas d'utilisation du chat (BBB_REDIS_CHANNEL) // Typiquement pour le répertoire 1 => chat1, 2 => chat2, 3 => chat3... - $channelRedis = "chat" . $idBbbLiveStreaming; - $cmdSed15 = "sed -i \"s/^.*BBB_REDIS_CHANNEL=.*/ - BBB_REDIS_CHANNEL=$channelRedis/\" " . $dockerFile; - exec("$cmdSed15 2>&1", $aRetourVerificationSed15, $sRetourVerificationSed15); - if ($sRetourVerificationSed15 != 0) { - writeLog(" - Commande '$cmdSed15' : $aRetourVerificationSed15[0]", "ERROR", __FILE__, __LINE__); + $channelRedis = "chat".$idBbbLiveStreaming; + $cmdSed15 = "sed -i \"s/^.*BBB_REDIS_CHANNEL=.*/ - BBB_REDIS_CHANNEL=$channelRedis/\" ".$dockerFile; + exec("$cmdSed15 2>&1", $aVerifSed15, $sVerifSed15); + if ($sVerifSed15 !== 0) { + writeLog( + " - Commande '$cmdSed15' : $aVerifSed15[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Modification de la ligne concernant le mode DEBUG (DEBUG) - if (DEBUG) { + // Modification de la ligne concernant le mode DEBUG (DEBUG). + if (DEBUG === true) { $debug = "true"; } else { $debug = "false"; } - $cmdSed16 = "sed -i \"s/^.*DEBUG=.*/ - DEBUG=$debug/\" " . $dockerFile; - exec("$cmdSed16 2>&1", $aRetourVerificationSed16, $sRetourVerificationSed16); - if ($sRetourVerificationSed16 != 0) { - writeLog(" - Commande '$cmdSed16' : $aRetourVerificationSed16[0]", "ERROR", __FILE__, __LINE__); + $cmdSed16 = "sed -i \"s/^.*DEBUG=.*/ - DEBUG=$debug/\" ".$dockerFile; + exec("$cmdSed16 2>&1", $aVerifSed16, $sVerifSed16); + if ($sVerifSed16 !== 0) { + writeLog( + " - Commande '$cmdSed16' : $aVerifSed16[0]", + "ERROR", __FILE__, __LINE__ + ); } - - /* Création du diffuseur correspondant dans Pod */ + // Création du diffuseur correspondant dans Pod. $cmdBroadcaster = "curl --silent -H 'Content-Type: multipart/form-data' "; - $cmdBroadcaster .= "-H 'Authorization: Token " . POD_TOKEN . "' "; - $cmdBroadcaster .= "-F 'url=" . checkEndSlash(POD_HLS_STREAM) . "$nameMeeting.m3u8' "; - $cmdBroadcaster .= "-F 'building=" . checkEndWithoutSlash(POD_URL) . "/rest/buildings/" . POD_ID_BUILDING . "/' "; - $cmdBroadcaster .= "-F 'name=$nameMeetingToDisplay' -F 'status=true' -F 'is_restricted=$isRestricted' '" . checkEndWithoutSlash(POD_URL) . "/rest/broadcasters/'"; - $verificationBroadcaster = exec("$cmdBroadcaster 2>&1", $aRetourVerificationBroadcaster, $sRetourVerificationBroadcaster); - - writeLog(" + Création du diffuseur correspondant dans Pod", "DEBUG"); - - if ($sRetourVerificationBroadcaster == 0) { - writeLog(" - Commande '$cmdBroadcaster' : $aRetourVerificationBroadcaster[0]", "DEBUG"); - // Id du diffuseur + $cmdBroadcaster .= "-H 'Authorization: Token ".POD_TOKEN."' "; + $cmdBroadcaster .= "-F 'url=".checkEndSlash(POD_HLS_STREAM)."$nameMeeting.m3u8' "; + $cmdBroadcaster .= "-F 'building=".checkEndWithoutSlash(POD_URL)."/rest/buildings/".POD_ID_BUILDING."/' "; + $cmdBroadcaster .= "-F 'name=$nameMeetingToDisplay' -F 'status=true' -F 'is_restricted=$isRestricted' '".checkEndWithoutSlash(POD_URL)."/rest/broadcasters/'"; + exec("$cmdBroadcaster 2>&1", $aVerifBroadcaster, $sVerifBroadcaster); + + writeLog( + " + Création du diffuseur correspondant dans Pod", + "DEBUG" + ); + + if ($sVerifBroadcaster === 0) { + writeLog( + " - Commande '$cmdBroadcaster' : $aVerifBroadcaster[0]", + "DEBUG" + ); + // Id du diffuseur. $idBroadcaster = 0; - // Récupération du diffuseur créé - $oBroadcaster = json_decode($aRetourVerificationBroadcaster[0]); + // Récupération du diffuseur créé. + $oBroadcaster = json_decode($aVerifBroadcaster[0]); - // Si le diffuseur existe déjà, $aRetourVerificationBroadcaster[0] contiendra un message d'avertissement du type : - // {"url":["Un objet Diffuseur avec ce champ URL existe déjà."],"name":["Un objet Diffuseur avec ce champ nom existe déjà."]} - if (strpos($aRetourVerificationBroadcaster[0], "Un objet Diffuseur avec ce champ nom existe déjà.") !== false) { + /* + Si le diffuseur existe déjà, + $aVerifBroadcaster[0] contiendra un message d'avertissement du type : + {"url":["Un objet Diffuseur avec ce champ URL existe déjà."], + "name":["Un objet Diffuseur avec ce champ nom existe déjà."]} + */ + + if (strpos($aVerifBroadcaster[0], "Un objet Diffuseur avec ce champ nom existe déjà.") !== false) { $cmdBroadcaster2 = "curl --silent -H 'Content-Type: application/json' "; - $cmdBroadcaster2 .= "-H 'Authorization: Token " . POD_TOKEN . "' "; - $cmdBroadcaster2 .= "-X GET " . checkEndWithoutSlash(POD_URL) . "/rest/broadcasters/$nameMeeting/"; - $verificationBroadcaster2 = exec("$cmdBroadcaster2 2>&1", $aRetourVerificationBroadcaster2, $sRetourVerificationBroadcaster2); - - writeLog(" + Récupération du diffuseur déjà existant dans Pod", "DEBUG"); - if ($sRetourVerificationBroadcaster2 == 0) { - writeLog(" - Commande '$cmdBroadcaster2' : $aRetourVerificationBroadcaster2[0]", "DEBUG"); - $oBroadcaster2 = json_decode($aRetourVerificationBroadcaster2[0]); + $cmdBroadcaster2 .= "-H 'Authorization: Token ".POD_TOKEN."' "; + $cmdBroadcaster2 .= "-X GET ".checkEndWithoutSlash(POD_URL)."/rest/broadcasters/$nameMeeting/"; + exec("$cmdBroadcaster2 2>&1", $aVerifBroadcaster2, $sVerifBroadcaster2); + + writeLog( + " + Récupération du diffuseur déjà existant dans Pod", + "DEBUG" + ); + if ($sVerifBroadcaster2 === 0) { + writeLog( + " - Commande '$cmdBroadcaster2' : $aVerifBroadcaster2[0]", + "DEBUG" + ); + $oBroadcaster2 = json_decode($aVerifBroadcaster2[0]); $idBroadcaster = $oBroadcaster2->id; } else { - writeLog(" + Commande '$cmdBroadcaster2' : $aRetourVerificationBroadcaster2[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdBroadcaster2' : $aVerifBroadcaster2[0]", + "ERROR", __FILE__, __LINE__ + ); } } else { $idBroadcaster = $oBroadcaster->id; } - if ($idBroadcaster != 0) { - writeLog(" + Utilisation du diffuseur $idBroadcaster", "DEBUG"); - - // Démarrage du live, si nécessaire - startLive($idLive, checkEndSlash(BBB_STREAM_URL) . "$nameMeeting", $idBbbLiveStreaming, $idBroadcaster); + if ($idBroadcaster !== 0) { + writeLog( + " + Utilisation du diffuseur $idBroadcaster", + "DEBUG" + ); + + // Démarrage du live, si nécessaire. + startLive( + $idLive, checkEndSlash(BBB_STREAM_URL)."$nameMeeting", + $idBbbLiveStreaming, $idBroadcaster + ); } else { - writeLog(" + Démarrage impossible du live : aucun identifiant du diffuseur défini.", "ERROR", __FILE__, __LINE__); + writeLog( + " + Démarrage impossible du live : aucun identifiant du diffuseur défini.", + "ERROR", __FILE__, __LINE__ + ); } } else { - writeLog(" + Commande '$cmdBroadcaster' : $aRetourVerificationBroadcaster[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdBroadcaster' : $aVerifBroadcaster[0]", + "ERROR", __FILE__, __LINE__ + ); } } else { - writeLog(" + Commande '$cmd' : $sRetourVerification[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmd' : $sVerif[0]", + "ERROR", __FILE__, __LINE__ + ); } } /** * Procédure permettant de démarrer un direct. - * @param string $idLive - Identifiant du live BBB de Pod à démarrer (cf. table bbb_meeting) - * @param string $streamName - Nom du stream utilisé, en correspondance avec le diffuseur créé précédemment - * @param string $idBbbLiveStreaming - Identifiant du répertoire bbb-live-streaming qui va être utilisé pour lancer ce direct - * @param string $idBroadcaster - Identifiant du diffuseur qui va être utilisé pour lancer ce direct + * + * @param string $idLive - Identifiant du live BBB de Pod à démarrer + * (cf. table bbb_meeting) + * @param string $streamName - Nom du stream utilisé, en correspondance + * avec le diffuseur créé précédemment + * @param string $idBbbLiveStreaming - Identifiant du répertoire bbb-live-streaming + * qui va être utilisé pour lancer ce direct + * @param string $idBroadcaster - Identifiant du diffuseur qui va être utilisé + * pour lancer ce direct */ function startLive($idLive, $streamName, $idBbbLiveStreaming, $idBroadcaster) { - writeLog("-----Démarrage du direct : startLive($idLive, '$streamName', $idBbbLiveStreaming, $idBroadcaster)-----", "DEBUG"); + writeLog( + "-----Démarrage du direct : startLive($idLive, '$streamName', $idBbbLiveStreaming, $idBroadcaster)-----", + "DEBUG" + ); if (DEBUG) { - // Avec gestions des logs, dans le répertoire des logs. Le nom du fichier correspond à l'id du live BBB de Pod ((cf. table bbb_meeting) - $cmd = "cd " . checkEndSlash(PHYSICAL_BASE_ROOT) . "bbb-live-streaming" . $idBbbLiveStreaming . " ; docker compose up 1>" . checkEndSlash(PHYSICAL_LOG_ROOT) . "$idLive.log"; - exec("$cmd 2>&1 &", $aRetourVerification, $sRetourVerification); + // Avec gestions des logs, dans le répertoire des logs. + // Le nom du fichier correspond à l'id du live BBB de Pod. + // ((cf. table bbb_meeting). + $cmd = "cd ".checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$idBbbLiveStreaming." ; docker compose up 1>".checkEndSlash(PHYSICAL_LOG_ROOT)."$idLive.log"; + exec("$cmd 2>&1 &", $aVerif, $sVerif); } else { - // En mode daemon - $cmd = "cd " . checkEndSlash(PHYSICAL_BASE_ROOT) . "bbb-live-streaming" . $idBbbLiveStreaming . " ; docker compose up -d"; - exec("$cmd 2>&1", $aRetourVerification, $sRetourVerification); + // En mode daemon. + $cmd = "cd ".checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$idBbbLiveStreaming." ; docker compose up -d"; + exec("$cmd 2>&1", $aVerif, $sVerif); } - writeLog("Démarrage du live", "DEBUG"); + writeLog( + "Démarrage du live", + "DEBUG" + ); - if ($sRetourVerification == 0) { - writeLog(" + Commande '$cmd'", "DEBUG"); + if ($sVerif === 0) { + writeLog( + " + Commande '$cmd'", + "DEBUG" + ); // Définition du port pour REDIS (en cas d'utilisation du chat) // Typiquement pour le répertoire 1 => 6379, 2 => 6380, 3 => 6381... - $portRedis = 6378 + $idBbbLiveStreaming; + $portRedis = (6378 + $idBbbLiveStreaming); // Définition du channel pour REDIS (en cas d'utilisation du chat) // Typiquement pour le répertoire 1 => chat1, 2 => chat2, 3 => chat3... - $channelRedis = "chat" . $idBbbLiveStreaming; + $channelRedis = "chat".$idBbbLiveStreaming; - // Mise à jour de l'information dans Pod, via l'API Rest + // Mise a jour de l'information dans Pod, via l'API Rest. $cmdMajPod = "curl --silent -H 'Content-Type: application/json' "; - $cmdMajPod .= "-H 'Authorization: Token " . POD_TOKEN . "' "; - $cmdMajPod .= "-X PATCH -d '{\"server\":\"" . SERVER_NUMBER . "/" . $idBbbLiveStreaming . "\", \"status\":1, \"broadcaster_id\": $idBroadcaster, \"redis_hostname\":\"" . SERVER_HOSTNAME . "\", \"redis_port\": $portRedis, \"redis_channel\":\"$channelRedis\"}' "; - $cmdMajPod .= "" . checkEndWithoutSlash(POD_URL) . "/rest/bbb_livestream/$idLive/"; - exec("$cmdMajPod", $aRetourVerificationMajPod, $sRetourVerificationMajPod); - - writeLog(" + Mise à jour de l'information du bbb_livestream dans Pod", "DEBUG"); - - if ($sRetourVerificationMajPod == 0) { - writeLog(" - Commande '$cmdMajPod' : $aRetourVerificationMajPod[0]", "DEBUG"); + $cmdMajPod .= "-H 'Authorization: Token ".POD_TOKEN."' "; + $cmdMajPod .= "-X PATCH -d '{\"server\":\"".SERVER_NUMBER."/".$idBbbLiveStreaming."\", \"status\":1, \"broadcaster_id\": $idBroadcaster, \"redis_hostname\":\"".SERVER_HOSTNAME."\", \"redis_port\": $portRedis, \"redis_channel\":\"$channelRedis\"}' "; + $cmdMajPod .= "".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/$idLive/"; + exec("$cmdMajPod", $aVerifMajPod, $sVerifMajPod); + + writeLog( + " + Mise à jour de l'information du bbb_livestream dans Pod", + "DEBUG" + ); + + if ($sVerifMajPod === 0) { + writeLog( + " - Commande '$cmdMajPod' : $aVerifMajPod[0]", + "DEBUG" + ); } else { - writeLog(" - Commande '$cmdMajPod' : $sRetourVerificationMajPod[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " - Commande '$cmdMajPod' : $sVerifMajPod[0]", + "ERROR", __FILE__, __LINE__ + ); } } else { - writeLog(" + Commande '$cmd' : $sRetourVerification[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmd' : $sVerif[0]", + "ERROR", __FILE__, __LINE__ + ); } - sendEmail("[BBB-POD-LIVE] Démarrage d'un live", "Démarrage d'un direct (id : $idLive, stream : $streamName) sur le serveur " . SERVER_NUMBER); + sendEmail("[BBB-POD-LIVE] Démarrage d'un live", "Démarrage d'un direct (id : $idLive, stream : $streamName) sur le serveur ".SERVER_NUMBER); } /** - * Procédure permettant d'identifier et d'arrêter des directs dont la session BigBlueButton a été arrêtée. + * Procédure permettant d'identifier et d'arrêter des directs + * dont la session BigBlueButton a été arrêtée. */ function stopLives() { - writeLog("-----Arrêt des directs : stopLives()-----", "DEBUG"); - - // Checksum utile pour récupérer les informations des sessions en cours sur BigBlueButton/Scalelite - $checksum = sha1("getMeetings" . BBB_SECRET); - // Adresse utile pour récupérer les informations des sessions en cours sur BigBlueButton/Scalelite - $bbbUrlGetMeetings = checkEndWithoutSlash(BBB_URL) . "/getMeetings?checksum=" . $checksum; - // Variable permettant de connaitre les sessions en cours sur le serveur BigBlueButton/Scalelite + writeLog( + "-----Arrêt des directs : stopLives()-----", + "DEBUG" + ); + + // Checksum utile pour récupérer les informations des sessions + // en cours sur BigBlueButton/Scalelite. + $checksum = sha1("getMeetings".BBB_SECRET); + // Adresse utile pour récupérer les informations des sessions + // en cours sur BigBlueButton/Scalelite. + $bbbUrlGetMeetings = checkEndWithoutSlash(BBB_URL)."/getMeetings?checksum=".$checksum; + // Variable permettant de connaitre les sessions + // en cours sur le serveur BigBlueButton/Scalelite. $meetingsInProgressOnBBB = array(); - // On ne récupère les sessions du serveur BigBlueButton/Scalelite que s'il existe des lives en cours sur ce serveur + // On ne récupère les sessions du serveur BigBlueButton/Scalelite + // que s'il existe des lives en cours sur ce serveur. if (count($GLOBALS["livesInProgressOnThisServer"]) > 0) { $xml = simplexml_load_file($bbbUrlGetMeetings); - writeLog("Récupération des sessions depuis le serveur BigBlueButton/Scalelite", "DEBUG"); - if ($xml === false) { - writeLog(" + Impossible de se connecter au serveur BBB/Scalelite : $bbbUrlGetMeetings", "ERROR", __FILE__, __LINE__); + writeLog( + "Récupération des sessions depuis le serveur BigBlueButton/Scalelite", + "DEBUG" + ); + if ($xml === false) { + writeLog( + " + Impossible de se connecter au serveur BBB/Scalelite : $bbbUrlGetMeetings", + "ERROR", __FILE__, __LINE__ + ); } else { - writeLog(" + Requête sur le serveur BBB/Scalelite : $bbbUrlGetMeetings", "DEBUG"); + writeLog( + " + Requête sur le serveur BBB/Scalelite : $bbbUrlGetMeetings", + "DEBUG" + ); foreach ($xml->meetings->meeting as $meeting) { - // Ajout du meetingID au tableau des sessions BBB en cours + // Ajout du meetingID au tableau des sessions BBB en cours. $meetingsInProgressOnBBB[] = $meeting->meetingID; } } - // Recherche de tous les directs marqués comme étant en cours + // Recherche de tous les directs marqués comme étant en cours. foreach ($GLOBALS["livesInProgressOnThisServer"] as $ligneLiveInProgressOnThisServer) { - // Récupération du BBB_MEETING_ID correspondant dans le docker-compose.yml - $dockerFile = checkEndSlash(PHYSICAL_BASE_ROOT) . "bbb-live-streaming" . $ligneLiveInProgressOnThisServer->idBbbLiveStreaming . "/docker-compose.yml"; - $dockerDirectory = checkEndSlash(PHYSICAL_BASE_ROOT) . "bbb-live-streaming" . $ligneLiveInProgressOnThisServer->idBbbLiveStreaming; - $cmdGrep1="grep BBB_MEETING_ID $dockerFile| cut -d\"=\" -f2"; - $verificationGrep1 = exec("$cmdGrep1 2>&1", $aRetourVerificationGrep1, $sRetourVerificationGrep1); - if ($sRetourVerificationGrep1 == 0) { - writeLog(" + Commande '$cmdGrep1' : $aRetourVerificationGrep1[0]", "DEBUG"); - // Meeting ID correspondant - $bbbMeetingId = $aRetourVerificationGrep1[0]; - - // Recherche du nom du diffuseur correspondant (sauvegardé aussi dans BBB_MEETING_TITLE du fichier compose) + /* + Récupération du BBB_MEETING_ID + correspondant dans le docker-compose.yml. + */ + + $dockerFile = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$ligneLiveInProgressOnThisServer->idBbbLiveStreaming."/docker-compose.yml"; + $dockerDirectory = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming".$ligneLiveInProgressOnThisServer->idBbbLiveStreaming; + $cmdGrep1 = "grep BBB_MEETING_ID $dockerFile| cut -d\"=\" -f2"; + exec("$cmdGrep1 2>&1", $aVerifGrep1, $sVerifGrep1); + if ($sVerifGrep1 === 0) { + writeLog( + " + Commande '$cmdGrep1' : $aVerifGrep1[0]", + "DEBUG" + ); + // Meeting ID correspondant. + $bbbMeetingId = $aVerifGrep1[0]; + + // Recherche du nom du diffuseur correspondant. + // (sauvegardé aussi dans BBB_MEETING_TITLE du fichier compose). $broadcasterName = ""; - $cmdGrep2="grep BBB_MEETING_TITLE $dockerFile| cut -d\"=\" -f2"; - $verificationGrep2 = exec("$cmdGrep2 2>&1", $aRetourVerificationGrep2, $sRetourVerificationGrep2); - if ($sRetourVerificationGrep2 == 0) { - writeLog(" + Commande '$cmdGrep2' : $aRetourVerificationGrep2[0]", "DEBUG"); - // Nom du diffuseur correspondant - $broadcasterName = formatString($aRetourVerificationGrep2[0]); + $cmdGrep2 = "grep BBB_MEETING_TITLE $dockerFile| cut -d\"=\" -f2"; + exec("$cmdGrep2 2>&1", $aVerifGrep2, $sVerifGrep2); + if ($sVerifGrep2 === 0) { + writeLog( + " + Commande '$cmdGrep2' : $aVerifGrep2[0]", + "DEBUG" + ); + // Nom du diffuseur correspondant. + $broadcasterName = formatString($aVerifGrep2[0]); } else { - writeLog(" + Commande '$cmdGrep2' : $sRetourVerificationGrep2[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdGrep2' : $sVerifGrep2[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Cet ID n'est plus dans la liste des sessions en cours sur BBB : - // - on arrête le container docker correspondant - // - on supprime le diffuseur correspondant - // - on copie, pour permettre l'encodage, le fichier vidéo si l'utilisateur a enregistré la session - if (! in_array($bbbMeetingId, $meetingsInProgressOnBBB)) { - writeLog(" + La session BigBlueButton $bbbMeetingId est arrêtée. Arrêt du container docker $dockerFile, suppression du diffuseur correspondant, copie du fichier vidéo généré selon le souhait de l'utilisateur", "INFO"); + /* + * Cet ID n'est plus dans la liste des sessions en cours sur BBB : + * - on arrête le container docker correspondant + * - on supprime le diffuseur correspondant + * - on copie, pour permettre l'encodage, le fichier vidéo + * si l'utilisateur a enregistré la session + */ + + if (in_array($bbbMeetingId, $meetingsInProgressOnBBB) === false) { + writeLog( + " + La session BigBlueButton $bbbMeetingId est arrêtée. Arrêt du container docker $dockerFile, suppression du diffuseur correspondant, copie du fichier vidéo généré selon le souhait de l'utilisateur", + "INFO" + ); $cmdStop = "cd $dockerDirectory; docker compose down"; - exec("$cmdStop 2>&1", $aRetourVerificationStop, $sRetourVerificationStop); - if ($sRetourVerificationStop == 0) { - writeLog(" - Le container docker $dockerDirectory est bien arrêté", "DEBUG"); - // Formatage de la date d'arrêt dans le bon format + // exec($command, $output, $result_code): string|false + exec("$cmdStop 2>&1", null, $sVerifStop); + if ($sVerifStop === 0) { + writeLog( + " - Le container docker $dockerDirectory est bien arrêté", + "DEBUG" + ); + // Formatage de la date d'arrêt dans le bon format. $endDate = date('Y-m-d H:i:s'); - // On sauvegarde cette information dans la base de Pod via l'appel à l'API Rest + // On sauvegarde cette information dans la base de Pod + // via l'appel à l'API Rest. $cmdMajPod1 = "curl --silent -H 'Content-Type: application/json' "; - $cmdMajPod1 .= "-H 'Authorization: Token " . POD_TOKEN . "' "; + $cmdMajPod1 .= "-H 'Authorization: Token ".POD_TOKEN."' "; $cmdMajPod1 .= "-X PATCH -d '{\"end_date\":\"$endDate\", \"status\":2}' "; - $cmdMajPod1 .= "" . checkEndWithoutSlash(POD_URL) . "/rest/bbb_livestream/" . $ligneLiveInProgressOnThisServer->id . "/"; - exec("$cmdMajPod1", $aRetourVerificationMajPod1, $sRetourVerificationMajPod1); + $cmdMajPod1 .= "".checkEndWithoutSlash(POD_URL)."/rest/bbb_livestream/".$ligneLiveInProgressOnThisServer->id."/"; + exec("$cmdMajPod1", $aVerifMajPod1, $sVerifMajPod1); - writeLog(" + Mise à jour de l'information du bbb_livestream dans Pod", "DEBUG"); + writeLog( + " + Mise à jour de l'information du bbb_livestream dans Pod", + "DEBUG" + ); - // URL de l'API Rest du meeting en cours d'arrêt + // URL de l'API Rest du meeting en cours d'arrêt. $urlApiRestMeeting = ""; - // URL de l'API Rest du user qui a réalisé le live qui en cours d'arrêt + // URL de l'API Rest du user qui a réalisé le live en cours d'arrêt. $urlApiRestUser = ""; - if ($sRetourVerificationMajPod1 == 0) { - writeLog(" - Commande '$cmdMajPod1' : $aRetourVerificationMajPod1[0]", "DEBUG"); - $oLive = json_decode($aRetourVerificationMajPod1[0]); - if (isset($oLive->meeting)) { + if ($sVerifMajPod1 === 0) { + writeLog( + " - Commande '$cmdMajPod1' : $aVerifMajPod1[0]", + "DEBUG" + ); + $oLive = json_decode($aVerifMajPod1[0]); + if (isset($oLive->meeting) === true) { $urlApiRestMeeting = $oLive->meeting; $urlApiRestUser = $oLive->user; } } else { - writeLog(" - Commande '$cmdMajPod1' : $sRetourVerificationMajPod1[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " - Commande '$cmdMajPod1' : $sVerifMajPod1[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Suppression du diffuseur - if ($broadcasterName != "") { + // Suppression du diffuseur. + if ($broadcasterName !== "") { deleteBroadcaster($broadcasterName); } - // Recherche si l'utilisateur a souhaité cet enregistrement (sauvegardé aussi dans BBB_DOWNLOAD_MEETING du fichier compose) + // Recherche si l'utilisateur a souhaité cet enregistrement. + // (sauvegardé aussi dans BBB_DOWNLOAD_MEETING du fichier compose). $downloadMeeting = false; - $cmdGrep3="grep BBB_DOWNLOAD_MEETING $dockerFile| cut -d\"=\" -f2"; - $verificationGrep3 = exec("$cmdGrep3 2>&1", $aRetourVerificationGrep3, $sRetourVerificationGrep3); - if ($sRetourVerificationGrep3 == 0) { - writeLog(" + Commande '$cmdGrep3' : $aRetourVerificationGrep3[0]", "DEBUG"); - // Nom du diffuseur correspondant - if ($aRetourVerificationGrep3[0] == "true") { + $cmdGrep3 = "grep BBB_DOWNLOAD_MEETING $dockerFile| cut -d\"=\" -f2"; + exec("$cmdGrep3 2>&1", $aVerifGrep3, $sVerifGrep3); + if ($sVerifGrep3 === 0) { + writeLog( + " + Commande '$cmdGrep3' : $aVerifGrep3[0]", + "DEBUG" + ); + // Nom du diffuseur correspondant. + if ($aVerifGrep3[0] === "true") { $downloadMeeting = true; } } else { - writeLog(" + Commande '$cmdGrep3' : $sRetourVerificationGrep3[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdGrep3' : $sVerifGrep3[0]", + "ERROR", __FILE__, __LINE__ + ); } - // Copie du fichier vidéo créé : si c'est configuré pour et que l'utilisateur a souhaité cet enregistrement - if (POD_DEFAULT_BBB_PATH != "" && $downloadMeeting) { - // Recherche de internal_meeting_id correspondant à cette session + // Copie du fichier vidéo créé : si c'est configuré pour + // et que l'utilisateur a souhaité cet enregistrement. + if (POD_DEFAULT_BBB_PATH !== "" && $downloadMeeting === true) { + /* + Recherche de internal_meeting_id + correspondant à cette session. + */ + $cmdMajPod2 = "curl --silent -H 'Content-Type: application/json' "; - $cmdMajPod2 .= "-H 'Authorization: Token " . POD_TOKEN . "' "; + $cmdMajPod2 .= "-H 'Authorization: Token ".POD_TOKEN."' "; $cmdMajPod2 .= "-X PATCH -d '{\"encoded_by\":\"$urlApiRestUser\", \"encoding_step\":3}' "; $cmdMajPod2 .= "$urlApiRestMeeting"; - $verificationMajPod2 = exec("$cmdMajPod2 2>&1", $aRetourVerificationMajPod2, $sRetourVerificationMajPod2); + exec("$cmdMajPod2 2>&1", $aVerifMajPod2, $sVerifMajPod2); - writeLog(" + Récupération de l'internal_meeting_id correspondant à l'objet bbb_meeting $bbbMeetingId depuis Pod", "DEBUG"); + writeLog( + " + Récupération de l'internal_meeting_id correspondant à l'objet bbb_meeting $bbbMeetingId depuis Pod", + "DEBUG" + ); $internalMeetingId = ""; - if ($sRetourVerificationMajPod2 == 0) { - writeLog(" - Commande '$cmdMajPod2' : $aRetourVerificationMajPod2[0]", "DEBUG"); - - // Recherche de l'internal_meeting_id correspondant au meeting - $oMeeting = json_decode($aRetourVerificationMajPod2[0]); - if (isset($oMeeting->internal_meeting_id)) { + if ($sVerifMajPod2 === 0) { + writeLog( + " - Commande '$cmdMajPod2' : $aVerifMajPod2[0]", + "DEBUG" + ); + + /* + Recherche de l'internal_meeting_id + correspondant au meeting. + */ + + $oMeeting = json_decode($aVerifMajPod2[0]); + if (isset($oMeeting->internal_meeting_id) === true) { $internalMeetingId = $oMeeting->internal_meeting_id; } } else { - writeLog(" - Commande '$cmdMajPod2' : $sRetourVerificationMajPod2[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " - Commande '$cmdMajPod2' : $sVerifMajPod2[0]", + "ERROR", __FILE__, __LINE__ + ); } - if ($internalMeetingId != "") { - processDirectory($ligneLiveInProgressOnThisServer->idBbbLiveStreaming, $internalMeetingId); + if ($internalMeetingId !== "") { + processDirectory( + $ligneLiveInProgressOnThisServer->idBbbLiveStreaming, + $internalMeetingId + ); } else { - writeLog(" - Impossible de récupérer l'internal meeting id pour le direct " . $ligneLiveInProgressOnThisServer->idBbbLiveStreaming, "ERROR", __FILE__, __LINE__); + writeLog( + " - Impossible de récupérer l'internal meeting id pour le direct ".$ligneLiveInProgressOnThisServer->idBbbLiveStreaming, + "ERROR", __FILE__, __LINE__ + ); } } } else { - writeLog(" + Commande '$cmdStop' : $sRetourVerificationStop[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdStop' : $sVerifStop[0]", + "ERROR", __FILE__, __LINE__ + ); } } } else { - writeLog(" + Commande '$cmdGrep1' : $sRetourVerificationGrep1[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdGrep1' : $sVerifGrep1[0]", + "ERROR", __FILE__, __LINE__ + ); } } } @@ -701,54 +1089,84 @@ function stopLives() /** * Procédure permettant de supprimer un diffuseur dans Pod. + * * @param string $broadcasterName - Nom du diffuseur à supprimer */ function deleteBroadcaster($broadcasterName) { - // Via l'API, il faut utiliser le slug et non le nom + // Via l'API, il faut utiliser le slug et non le nom. $slug = str_replace("[BBB]", "bbb", $broadcasterName); - /* Suppression du diffuseur correspondant dans Pod */ + // Suppression du diffuseur correspondant dans Pod. $cmdBroadcaster = "curl --silent "; - $cmdBroadcaster .= "-H 'Authorization: Token " . POD_TOKEN . "' "; - $cmdBroadcaster .= "-X DELETE '" . checkEndWithoutSlash(POD_URL) . "/rest/broadcasters/$slug/'"; - $verificationBroadcaster = exec("$cmdBroadcaster 2>&1", $aRetourVerificationBroadcaster, $sRetourVerificationBroadcaster); - - writeLog(" + Suppression du diffuseur $slug dans Pod", "DEBUG"); - - if ($sRetourVerificationBroadcaster == 0) { - writeLog(" - Commande '$cmdBroadcaster' exécutée", "DEBUG"); + $cmdBroadcaster .= "-H 'Authorization: Token ".POD_TOKEN."' "; + $cmdBroadcaster .= "-X DELETE '".checkEndWithoutSlash(POD_URL)."/rest/broadcasters/$slug/'"; + exec("$cmdBroadcaster 2>&1", $aVerifBroadcaster, $sVerifBroadcaster); + + writeLog( + " + Suppression du diffuseur $slug dans Pod", + "DEBUG" + ); + + if ($sVerifBroadcaster === 0) { + writeLog( + " - Commande '$cmdBroadcaster' exécutée", + "DEBUG" + ); } else { - writeLog(" + Commande '$cmdBroadcaster' : $aRetourVerificationBroadcaster[0]", "ERROR", __FILE__, __LINE__); + writeLog( + " + Commande '$cmdBroadcaster' : $aVerifBroadcaster[0]", + "ERROR", __FILE__, __LINE__ + ); } } /** - * Procédure permettant de copier le fichier vidéo créé, si un enregistrement existe de ce dernier. - * Le fichier vidéo est créé dans le répertoire videodata du répertoire BigBlueButton-liveStreaming concerné. - * Ce fichier vidéo sera copié dans le POD_DEFAULT_BBB_PATH et renommé sous la forme internalMeetingId.mkv. - * Ce nommage est très important et permet au CRON Job bbb de Pod d'assigner le bon propriétaire à cette vidéo. - * @param string $idBbbLiveStreaming - Identifiant du répertoire BigBlueButton-liveStreaming concerné. - * @param string $internalMeetingId - Identifiant interne de la session BBB enregistrée. + * Procédure permettant de copier le fichier vidéo créé, + * si un enregistrement existe de ce dernier. + * Le fichier vidéo est créé dans le répertoire videodata + * du répertoire BigBlueButton-liveStreaming concerné. + * Ce fichier vidéo sera copié dans le POD_DEFAULT_BBB_PATH + * et renommé sous la forme internalMeetingId.mkv. + * Ce nommage est très important et permet au CRON Job bbb de Pod + * d'assigner le bon propriétaire à cette vidéo. + * + * @param string $idBbbLiveStreaming - Identifiant du répertoire + * BigBlueButton-liveStreaming concerné. + * @param string $internalMeetingId - Identifiant interne de la + * session BBB enregistrée. */ function processDirectory($idBbbLiveStreaming, $internalMeetingId) { - writeLog("-----Copie des fichiers vidéos enregistrées sur le partage NFS, pour traitement automatique par POD : processDirectory($idBbbLiveStreaming, $internalMeetingId)-----", "DEBUG"); - // Parcours du répertoire videodata du répertoire BigBlueButton-liveStreaming concerné - // Définition du répertoire - $directoryLiveStreaming = checkEndSlash(PHYSICAL_BASE_ROOT) . "bbb-live-streaming$idBbbLiveStreaming/videodata"; - writeLog("Recherche de fichiers vidéos pour le direct $idBbbLiveStreaming : $directoryLiveStreaming", "DEBUG"); - if (file_exists($directoryLiveStreaming)) { - $listFiles = scandir("$directoryLiveStreaming"); - // Mise en place d'une boucle, mais il ne doit y avoir qu'un seul fichier au maximum + writeLog( + "-----Copie des fichiers vidéos enregistrées sur le partage NFS, pour traitement automatique par POD : processDirectory($idBbbLiveStreaming, $internalMeetingId)-----", + "DEBUG" + ); + // Parcours du répertoire videodata du répertoire + // BigBlueButton-liveStreaming concerné + // Définition du répertoire. + $dirLiveStreaming = checkEndSlash(PHYSICAL_BASE_ROOT)."bbb-live-streaming$idBbbLiveStreaming/videodata"; + writeLog( + "Recherche de fichiers vidéos pour le direct $idBbbLiveStreaming : $dirLiveStreaming", + "DEBUG" + ); + + if (is_dir($dirLiveStreaming) === true) { + $listFiles = scandir("$dirLiveStreaming"); + // Mise en place d'une boucle, + // mais il ne doit y avoir qu'un seul fichier au maximum. foreach ($listFiles as $key => $value) { - if (strrpos($value, ".mkv")) { - // Déplacer et renommer le fichier avec l'internalMeetingId - $oldFilename = "$directoryLiveStreaming/$value"; - $newFilename = checkEndSlash(POD_DEFAULT_BBB_PATH) . "$internalMeetingId" . ".mkv"; - writeLog(" + Déplacement du fichier $oldFilename vers $newFilename", "DEBUG"); + if (strrpos($value, ".mkv") === true) { + // Déplacer et renommer le fichier avec l'internalMeetingId. + $oldFilename = "$dirLiveStreaming/$value"; + $newFilename = checkEndSlash(POD_DEFAULT_BBB_PATH).$internalMeetingId.".mkv"; + writeLog( + " + Déplacement du fichier $oldFilename vers $newFilename", + "DEBUG" + ); @rename("$oldFilename", "$newFilename"); - // Positionnement de droits adéquats pour pouvoir être encodé par Pod - // Normalement, il n'y en a pas besoin : le fichier généré a les droits 644, ce qui est suffisant. + // Positionnement de droits adéquats pour pouvoir être encodé par Pod. + // Normalement, il n'y en a pas besoin : + // le fichier généré a les droits 644, ce qui est suffisant. @chmod("$newFilename", 0755); } } @@ -757,51 +1175,56 @@ function processDirectory($idBbbLiveStreaming, $internalMeetingId) /** * Fonction d'écriture dans le fichier de logs. - * Les messages au niveau debug ne seront écris que si l'application est configuré en mode DEBUG (DEBUG = true). + * Les messages au niveau debug ne seront écris que + * si l'application est configuré en mode DEBUG (DEBUG = true). + * * @param string $message - Message à écrire - * @param string $level - Niveau de log de ce message (debug, info, warning, error) - * @param string $file - Nom du fichier PHP concerné (en cas d'erreur) - * @param int $line - Ligne dans le fichier PHP concerné (en cas d'erreur) + * @param string $level - Niveau de log de ce message (debug, info, warning, error) + * @param string $file - Nom du fichier PHP concerné (en cas d'erreur) + * @param int $line - Ligne dans le fichier PHP concerné (en cas d'erreur) + * * @return nombre d'octets écris, false sinon */ -function writeLog($message, $level, $file = null, $line = null) +function writeLog($message, $level, $file=null, $line=null) { - // Ecriture des lignes de debug seulement en cas de mode DEBUG - if (($level == "DEBUG") && (! DEBUG)) { + // Ecriture des lignes de debug seulement en cas de mode DEBUG. + if (($level === "DEBUG") && (DEBUG === false)) { return false; } - // Création du répertoire des logs si besoin - if (! file_exists(checkEndSlash(PHYSICAL_LOG_ROOT))) { - // Création du répertoire + // Création du répertoire des logs si besoin. + if (is_dir(checkEndSlash(PHYSICAL_LOG_ROOT)) === false) { + // Création du répertoire. @mkdir(checkEndSlash(PHYSICAL_LOG_ROOT), 0755); } - // Configuration du fichier de log, 1 par jour - $logFile = checkEndSlash(PHYSICAL_LOG_ROOT) . gmdate("Y-m-d") . "_bbb-pod-live.log"; + // Configuration du fichier de log, 1 par jour. + $logFile = checkEndSlash(PHYSICAL_LOG_ROOT).gmdate("Y-m-d")."_bbb-pod-live.log"; - // En cas de non existence, on créé ce fichier - if (!file_exists($logFile)) { + // En cas de non existence, on créé ce fichier. + if (file_exists($logFile) === false) { $file = fopen($logFile, "x+"); - // Une exception est levée en cas de non existence du fichier (problème manifeste de droits utilisateurs) - if (!file_exists($logFile)) { - echo "Erreur de configuration : impossible de créer le fichier $logFile."; + // Une exception est levée en cas de non existence du fichier. + // (problème manifeste de droits utilisateurs). + if (file_exists($logFile) === false) { + print("Erreur de configuration : impossible de créer le fichier $logFile."); throw new Exception("Impossible de créer le fichier $logFile."); } } - // Une exception est levée en cas de problème d'écriture (problème manifeste de droits utilisateurs) + // Une exception est levée en cas de problème d'écriture. + // (problème manifeste de droits utilisateurs) if (!is_writeable($logFile)) { throw new Exception("$logFile n'a pas les droits en écriture."); } - $message = gmdate("Y-m-d H:i:s") . " - [$level] - " . $message; + $message = gmdate("Y-m-d H:i:s")." - [$level] - ".$message; $message .= is_null($file) ? '' : " - Fichier [$file]"; $message .= is_null($line) ? '' : " - Ligne [$line]."; $message .= "\n"; - // Surcharge de la variable globale signifiant une erreur dans le script - if ($level == "ERROR") { + // Surcharge de la variable globale signifiant une erreur dans le script. + if ($level === "ERROR") { $GLOBALS["txtErrorInScript"] .= "$message"; } @@ -809,9 +1232,13 @@ function writeLog($message, $level, $file = null, $line = null) } /** - * Fonction permettant de vérifier que la chaîne de caractères finit par un /. En ajoute un si nécessaire. - * @param string - Chaîne de caractères. - * @return Chaîne de caractères identique à celle en entrée, mais avec un / comme dernier caractère. + * Fonction permettant de vérifier que la chaîne de caractères finit par un /. + * En ajoute un si nécessaire. + * + * @param string $string - Chaîne de caractères. + * + * @return Chaîne de caractères identique à celle en entrée, + * mais avec un / comme dernier caractère. */ function checkEndSlash($string) { @@ -822,28 +1249,37 @@ function checkEndSlash($string) } /** - * Fonction permettant de vérifier que la chaîne de caractères ne finit pas par un /. Supprime ce / un si nécessaire. - * @param string - Chaîne de caractères. + * Fonction permettant de vérifier que la chaîne de caractères + * ne finit pas par un /. Supprime ce / un si nécessaire. + * + * @param string $string - Chaîne de caractères. + * * @return Chaîne de caractères identique à celle en entrée, mais sans / à la fin. */ function checkEndWithoutSlash($string) { - if (substr($string, -1) == "/") { + if (substr($string, -1) === "/") { $string = substr($string, 0, -1); } return $string; } /** -* Fonction permettant de supprimer les caractères accentués et autres caractéres problématiques d'une chaîne de caractères. -* Remplace aussi les espaces par des tirets -* @param $string - chaîne avec accents -* @return chaîne sans accents -*/ + * Fonction permettant de supprimer les caractères accentués et autres caractères + * problématiques d'une chaîne de caractères. + * Remplace aussi les espaces par des tirets + * + * @param $string - chaîne avec accents + * + * @return chaîne sans accents + */ function formatString($string) { $string = htmlentities($string, ENT_NOQUOTES, 'utf-8'); - $string = preg_replace('#&([A-za-z])(?:uml|circ|tilde|acute|grave|cedil|ring);#', '\1', $string); + $string = preg_replace( + '#&([A-za-z])(?:uml|circ|tilde|acute|grave|cedil|ring);#', + '\1', $string + ); $string = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $string); $string = preg_replace('#&[^;]+;".()\'#', '', $string); $string = preg_replace('/\s+/', '-', $string); @@ -852,15 +1288,21 @@ function formatString($string) } /** -* Fonction permettant de supprimer les caractères accentués et autres caractéres problématiques d'une chaîne de caractères. -* Ne replace pas les espaces. -* @param $string - chaîne avec accents -* @return chaîne sans accents -*/ + * Fonction permettant de supprimer les caractères accentués et autres caractéres + * problématiques d'une chaîne de caractères. + * Ne replace pas les espaces. + * + * @param $string - chaîne avec accents + * + * @return chaîne sans accents + */ function formatStringToDisplay($string) { $string = htmlentities($string, ENT_NOQUOTES, 'utf-8'); - $string = preg_replace('#&([A-za-z])(?:uml|circ|tilde|acute|grave|cedil|ring);#', '\1', $string); + $string = preg_replace( + '#&([A-za-z])(?:uml|circ|tilde|acute|grave|cedil|ring);#', + '\1', $string + ); $string = preg_replace('#&([A-za-z]{2})(?:lig);#', '\1', $string); $string = preg_replace('#&[^;]+;".()\'#', '', $string); $string = str_replace("'", "-", $string); @@ -868,17 +1310,18 @@ function formatStringToDisplay($string) } /** -* Procédure permettant d'envoyer un email à l'administrateur. -* @param $subject - sujet du mail -* @param $message - message du mail -*/ + * Procédure permettant d'envoyer un email à l'administrateur. + * + * @param $subject - sujet du mail + * @param $message - message du mail + */ function sendEmail($subject, $message) { - $to = ADMIN_EMAIL; + $dest = ADMIN_EMAIL; $message = nl2br($message); $headers = "MIME-Version: 1.0\r\n"; $headers .= "Content-type: text/html; charset=utf-8\r\n"; - mail($to, $subject, $message, $headers); + mail($dest, $subject, $message, $headers); } diff --git a/setup.cfg b/setup.cfg index 1f62524fbf..38cae469b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [flake8] -exclude = .git,pod/*/migrations/*.py,test_settings.py,node_modules/*/*.py,pod/static/*.py,pod/custom/tenants/*/*.py +exclude = .git,pod/*/migrations/*.py,*_settings.py,node_modules/*/*.py,pod/static/*.py,pod/custom/tenants/*/*.py max-complexity = 7 max-line-length = 90 # See https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#flake8