Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
73154ae174 |
@@ -0,0 +1,12 @@
|
|||||||
|
Before reporting, make sure you check our [official documentation](https://docs.chamilo.org).
|
||||||
|
|
||||||
|
If you are fairly sure you found an issue, please use the following template (remove things in other languages than your own and remove these 2 first lines).
|
||||||
|
|
||||||
|
### Current behavior / Resultado actual / Résultat actuel
|
||||||
|
|
||||||
|
### Expected behavior / Resultado esperado / Résultat attendu
|
||||||
|
|
||||||
|
### Steps to reproduce / Pasos para reproducir / Étapes pour reproduire
|
||||||
|
|
||||||
|
### Chamilo Version / Versión de Chamilo / Version de Chamilo
|
||||||
|
|
||||||
@@ -0,0 +1,196 @@
|
|||||||
|
name: Behat tests 1.11.x 🐞
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: PHP ${{ matrix.php-versions }} Test on ${{ matrix.operating-system }}
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
operating-system: [ubuntu-latest]
|
||||||
|
php-versions: ['7.4']
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:latest
|
||||||
|
env:
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: false
|
||||||
|
MYSQL_ROOT_PASSWORD: root
|
||||||
|
MYSQL_DATABASE: chamilo
|
||||||
|
ports:
|
||||||
|
- 3306/tcp
|
||||||
|
options: >-
|
||||||
|
--health-cmd="mysqladmin ping"
|
||||||
|
--health-interval=10s
|
||||||
|
--health-timeout=5s
|
||||||
|
--health-retries=3
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Setup apache
|
||||||
|
run: |
|
||||||
|
sudo apt-get install software-properties-common
|
||||||
|
sudo add-apt-repository ppa:ondrej/php
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install apache2 php${{ matrix.php-versions }} php${{ matrix.php-versions }}-common php${{ matrix.php-versions }}-cli libapache2-mod-php${{ matrix.php-versions }}
|
||||||
|
sudo a2enmod rewrite actions
|
||||||
|
sudo cp -f tests/travis/gh-apache /etc/apache2/sites-available/000-default.conf
|
||||||
|
sudo chmod 777 -R $HOME
|
||||||
|
cat /etc/apache2/sites-available/000-default.conf
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
|
||||||
|
- name: Setup PHP, with composer and extensions
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
extensions: mbstring, xml, ctype, iconv, intl, pdo, pdo_mysql, mysqli, dom, gd, json, soap, zip, bcmath
|
||||||
|
ini-values: post_max_size=256M, max_execution_time=600, memory_limit=4096M
|
||||||
|
tools: composer:v2
|
||||||
|
|
||||||
|
- name: Get composer cache directory
|
||||||
|
id: composer-cache-v1
|
||||||
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache composer dependencies
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache-v1.outputs.dir }}
|
||||||
|
# Use composer.json for key, if composer.lock is not committed.
|
||||||
|
# key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
||||||
|
key: v1-${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||||
|
restore-keys: v1-${{ runner.os }}-composer-
|
||||||
|
|
||||||
|
- name: Install dependencies with composer
|
||||||
|
run: php7.4 /usr/local/bin/composer install --prefer-dist --no-progress
|
||||||
|
|
||||||
|
# - name: Install assets
|
||||||
|
# run: php bin/console assets:install
|
||||||
|
#
|
||||||
|
# - name: Generate fos_js_routes.json
|
||||||
|
# run: php bin/console fos:js-routing:dump --format=json --target=public/js/fos_js_routes.json
|
||||||
|
|
||||||
|
# - name: Get yarn cache directory
|
||||||
|
# id: yarn-cache
|
||||||
|
# run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||||
|
#
|
||||||
|
# - name: Cache yarn dependencies
|
||||||
|
# uses: actions/cache@v2
|
||||||
|
# with:
|
||||||
|
# path: ${{ steps.yarn-cache.outputs.dir }}
|
||||||
|
# # Use composer.json for key, if composer.lock is not committed.
|
||||||
|
# # key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
||||||
|
# key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
|
# restore-keys: ${{ runner.os }}-yarn-
|
||||||
|
#
|
||||||
|
# - name: Yarn install
|
||||||
|
# uses: borales/actions-yarn@v2.3.0
|
||||||
|
# with:
|
||||||
|
# cmd: install # will run `yarn install` command
|
||||||
|
#
|
||||||
|
# - name: Yarn run encore dev
|
||||||
|
# uses: borales/actions-yarn@v2.3.0
|
||||||
|
# with:
|
||||||
|
# cmd: run encore dev
|
||||||
|
|
||||||
|
- name: Install chrome
|
||||||
|
uses: browser-actions/setup-chrome@v1
|
||||||
|
id: setup-chrome
|
||||||
|
|
||||||
|
- name: Start chrome
|
||||||
|
run: |
|
||||||
|
${{ steps.setup-chrome.outputs.chrome-path }} --version
|
||||||
|
${{ steps.setup-chrome.outputs.chrome-path }} --headless --disable-gpu --remote-debugging-port=9222 http://localhost &
|
||||||
|
|
||||||
|
# https://github.com/marketplace/actions/setup-chromedriver
|
||||||
|
- name: Start chromedriver
|
||||||
|
uses: nanasess/setup-chromedriver@master
|
||||||
|
with:
|
||||||
|
# Optional: do not specify to match Chrome's version
|
||||||
|
chromedriver-version: '100.0.4896.60'
|
||||||
|
|
||||||
|
- run: |
|
||||||
|
export DISPLAY=:99
|
||||||
|
chromedriver --url-base=/wd/hub &
|
||||||
|
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional
|
||||||
|
|
||||||
|
# wget https://chromedriver.storage.googleapis.com/88.0.4324.96/chromedriver_linux64.zip --quiet && unzip chromedriver_linux64.zip
|
||||||
|
# sudo mv chromedriver /usr/bin
|
||||||
|
# sudo chmod +x /usr/bin/chromedriver
|
||||||
|
# chromedriver --version
|
||||||
|
|
||||||
|
# - run: |
|
||||||
|
# export DISPLAY=:99
|
||||||
|
# chromedriver --url-base=/wd/hub &
|
||||||
|
# sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 & # optional
|
||||||
|
# chromedriver --version
|
||||||
|
|
||||||
|
- name: Set up JDK 1.8
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: 1.8
|
||||||
|
|
||||||
|
- name: Get Selenium
|
||||||
|
run: |
|
||||||
|
wget http://selenium-release.storage.googleapis.com/3.141/selenium-server-standalone-3.141.59.jar --quiet
|
||||||
|
|
||||||
|
- name: Run Selenium
|
||||||
|
run: |
|
||||||
|
java -version
|
||||||
|
export DISPLAY=:99.0
|
||||||
|
sudo xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||||
|
java -Dwebdriver.chrome.driver=/usr/bin/chromedriver -jar selenium-server-standalone-3.141.59.jar -log selenium.log > /dev/null &
|
||||||
|
|
||||||
|
- name: Start mysql service
|
||||||
|
run: |
|
||||||
|
sudo /etc/init.d/mysql start
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
#
|
||||||
|
# - name: Install database
|
||||||
|
# run: |
|
||||||
|
# bin/console doctrine:database:create || echo "Error while creating the DB"
|
||||||
|
# env:
|
||||||
|
# DATABASE_URL: mysql://root:root@127.0.0.1:${{ job.services.mysql.ports['3306'] }}/chamilo
|
||||||
|
# DATABASE_HOST: 127.0.0.1
|
||||||
|
# DATABASE_PORT: ${{ job.services.mysql.ports['3306'] }}
|
||||||
|
# DATABASE_NAME: chamilo
|
||||||
|
# DATABASE_USER: root
|
||||||
|
# DATABASE_PASSWORD: root
|
||||||
|
# APP_INSTALLED: 1
|
||||||
|
|
||||||
|
- name: Check settings
|
||||||
|
run: |
|
||||||
|
php -ini
|
||||||
|
php -v
|
||||||
|
php -m
|
||||||
|
|
||||||
|
- name: Install chash
|
||||||
|
run: |
|
||||||
|
git clone https://github.com/chamilo/chash
|
||||||
|
cd chash
|
||||||
|
git checkout 0.2.x
|
||||||
|
php7.4 /usr/local/bin/composer install
|
||||||
|
php7.4 -d phar.readonly=0 createPhar.php
|
||||||
|
chmod +x chash.phar
|
||||||
|
sudo mv chash.phar /usr/local/bin/chash
|
||||||
|
|
||||||
|
- name: Install chamilo
|
||||||
|
run: |
|
||||||
|
php7.4 ${{ github.workspace }}/chash/chash.php chash:chamilo_install 1.11.x ${{ github.workspace }} --no-interaction --sitename="Chamilo" --site_url="http://localhost/" --institution="Chamilo" --institution_url="https://chamilo.org" --encrypt_method="sha1" --firstname="John" --lastname="Doe" --language="english" --driver="pdo_mysql" --host="localhost" --port="3306" --dbname="chamilo" --dbuser="root" --dbpassword="root" --permissions_for_new_directories="0777" --permissions_for_new_files="0666" --linux-user="www-data" --linux-group="www-data" --username="admin" --password="admin" --email="admin@example.com" --phone="555-5555"
|
||||||
|
php7.4 ${{ github.workspace }}/chash/chash.php chash:chamilo_status
|
||||||
|
sudo chmod -R 777 app/cache app/logs app/courses app/upload web
|
||||||
|
bash tests/travis/post_installation.sh
|
||||||
|
sudo cp -f tests/travis/gh-apache /etc/apache2/sites-available/000-default.conf
|
||||||
|
sudo systemctl restart apache2
|
||||||
|
curl http://localhost/index.php
|
||||||
|
|
||||||
|
- name: Behat tests
|
||||||
|
run: |
|
||||||
|
cd ${{ github.workspace }}/tests/behat
|
||||||
|
../../vendor/behat/behat/bin/behat -vvv
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
name: PHP-CS-Fixer
|
||||||
|
|
||||||
|
on: [ push, pull_request ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
composer_install:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
php-versions: ['7.4']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 2
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
extensions: mbstring, xml, ctype, iconv, intl, pdo, pdo_mysql, dom, gd, json, soap, zip, bcmath
|
||||||
|
ini-values: post_max_size=256M, max_execution_time=600, memory_limit=4096M
|
||||||
|
|
||||||
|
- name: Get composer cache directory
|
||||||
|
id: composer-cache
|
||||||
|
run: echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache composer downloads
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}
|
||||||
|
restore-keys: ${{ runner.os }}-composer-
|
||||||
|
|
||||||
|
- name: Run composer install
|
||||||
|
run: composer install --prefer-dist --no-progress
|
||||||
|
|
||||||
|
- name: Download PHP-CS-Fixer
|
||||||
|
run: |
|
||||||
|
wget https://github.com/FriendsOfPHP/PHP-CS-Fixer/releases/download/v3.4.0/php-cs-fixer.phar
|
||||||
|
|
||||||
|
- name: Run PHP-CS-Fixer
|
||||||
|
run: |
|
||||||
|
IFS='
|
||||||
|
'
|
||||||
|
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRTUXB "HEAD~..HEAD")
|
||||||
|
if ! echo "${CHANGED_FILES}" | grep -qE "^(\\.php-cs-fixer(\\.dist)?\\.php|composer\\.lock)$"; then EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "${CHANGED_FILES}"); else EXTRA_ARGS=''; fi
|
||||||
|
php ./php-cs-fixer.phar fix --config=.php-cs-fixer.php -v --dry-run --using-cache=no --diff ${EXTRA_ARGS}
|
||||||
@@ -34,6 +34,21 @@ if (empty($certificateData)) {
|
|||||||
api_not_allowed(false, Display::return_message(get_lang('NoCertificateAvailable'), 'warning'));
|
api_not_allowed(false, Display::return_message(get_lang('NoCertificateAvailable'), 'warning'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Access control: only the owner, a platform admin, or a teacher of the certificate's course
|
||||||
|
// may view or export a certificate. Compare against $certificate->user_id (set from DB in the
|
||||||
|
// constructor) — not the $userId GET parameter, which an attacker can spoof to their own ID
|
||||||
|
// while supplying someone else's certificate $id.
|
||||||
|
$currentUserId = api_get_user_id();
|
||||||
|
if (!api_is_anonymous() && (int) $currentUserId !== (int) $certificate->user_id) {
|
||||||
|
$isCourseTeacher = false;
|
||||||
|
if (!empty($category) && !empty($category->get_course_code())) {
|
||||||
|
$isCourseTeacher = CourseManager::is_course_teacher($currentUserId, $category->get_course_code());
|
||||||
|
}
|
||||||
|
if (!api_is_platform_admin() && !$isCourseTeacher) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CustomCertificatePlugin::redirectCheck($certificate, $certificateId, $userId);
|
CustomCertificatePlugin::redirectCheck($certificate, $certificateId, $userId);
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
|
|||||||
+7
-3
@@ -58,7 +58,7 @@
|
|||||||
"doctrine/orm": "~2.5",
|
"doctrine/orm": "~2.5",
|
||||||
"emojione/emojione": "1.3.0",
|
"emojione/emojione": "1.3.0",
|
||||||
"endroid/qr-code": "2.5.*",
|
"endroid/qr-code": "2.5.*",
|
||||||
"enshrined/svg-sanitize": "^0.16.0",
|
"enshrined/svg-sanitize": "^0.22.0",
|
||||||
"essence/essence": "2.6.1",
|
"essence/essence": "2.6.1",
|
||||||
"ezyang/htmlpurifier": "~4.9",
|
"ezyang/htmlpurifier": "~4.9",
|
||||||
"facebook/graph-sdk": "^5.7",
|
"facebook/graph-sdk": "^5.7",
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
"phpoffice/phpspreadsheet": "^1.28",
|
"phpoffice/phpspreadsheet": "^1.28",
|
||||||
"phpoffice/phpword": "~0.14",
|
"phpoffice/phpword": "~0.14",
|
||||||
"phpseclib/phpseclib": "^2.0",
|
"phpseclib/phpseclib": "^2.0",
|
||||||
"robrichards/xmlseclibs": "3.0.*",
|
"robrichards/xmlseclibs": "^3.1.5",
|
||||||
"sabre/vobject": "~3.1",
|
"sabre/vobject": "~3.1",
|
||||||
"sonata-project/admin-bundle": "~3.1|~4.0",
|
"sonata-project/admin-bundle": "~3.1|~4.0",
|
||||||
"sonata-project/core-bundle": "~3.1|~4.0",
|
"sonata-project/core-bundle": "~3.1|~4.0",
|
||||||
@@ -107,6 +107,7 @@
|
|||||||
"sylius/attribute": "0.13.0",
|
"sylius/attribute": "0.13.0",
|
||||||
"sylius/translation": "0.13.0",
|
"sylius/translation": "0.13.0",
|
||||||
"symfony/console": "~3.0|~4.0",
|
"symfony/console": "~3.0|~4.0",
|
||||||
|
"symfony/css-selector": "~3.4|~4.0",
|
||||||
"symfony/doctrine-bridge": "~2.8",
|
"symfony/doctrine-bridge": "~2.8",
|
||||||
"symfony/dom-crawler": "~3.4|~4.0",
|
"symfony/dom-crawler": "~3.4|~4.0",
|
||||||
"symfony/filesystem": "~3.0|~4.0",
|
"symfony/filesystem": "~3.0|~4.0",
|
||||||
@@ -197,6 +198,9 @@
|
|||||||
],
|
],
|
||||||
"config": {
|
"config": {
|
||||||
"sort-packages": true,
|
"sort-packages": true,
|
||||||
"component-dir": "web/assets"
|
"component-dir": "web/assets",
|
||||||
|
"audit": {
|
||||||
|
"block-insecure": false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+213
-205
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "f85de82f07063e8ad5963e4816bd3eb5",
|
"content-hash": "07abb2f1f2575b4339f7eb5c2e96702d",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "alchemy/binary-driver",
|
"name": "alchemy/binary-driver",
|
||||||
@@ -128,16 +128,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "apereo/phpcas",
|
"name": "apereo/phpcas",
|
||||||
"version": "1.6.1",
|
"version": "1.6.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/apereo/phpCAS.git",
|
"url": "https://github.com/apereo/phpCAS.git",
|
||||||
"reference": "c129708154852656aabb13d8606cd5b12dbbabac"
|
"reference": "b413d2981947fd4f97079f715e0571d0c8f2a2c5"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/apereo/phpCAS/zipball/c129708154852656aabb13d8606cd5b12dbbabac",
|
"url": "https://api.github.com/repos/apereo/phpCAS/zipball/b413d2981947fd4f97079f715e0571d0c8f2a2c5",
|
||||||
"reference": "c129708154852656aabb13d8606cd5b12dbbabac",
|
"reference": "b413d2981947fd4f97079f715e0571d0c8f2a2c5",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -193,9 +193,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/apereo/phpCAS/issues",
|
"issues": "https://github.com/apereo/phpCAS/issues",
|
||||||
"source": "https://github.com/apereo/phpCAS/tree/1.6.1"
|
"source": "https://github.com/apereo/phpCAS/tree/1.6.2"
|
||||||
},
|
},
|
||||||
"time": "2023-02-19T19:52:35+00:00"
|
"time": "2026-04-22T09:20:37+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
@@ -612,32 +612,34 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"abandoned": true,
|
||||||
"time": "2022-01-17T14:14:24+00:00"
|
"time": "2022-01-17T14:14:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "composer/pcre",
|
"name": "composer/pcre",
|
||||||
"version": "3.3.2",
|
"version": "3.4.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/composer/pcre.git",
|
"url": "https://github.com/composer/pcre.git",
|
||||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
|
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
"url": "https://api.github.com/repos/composer/pcre/zipball/d5a341b3fb61f3001970940afb1d332968a183ed",
|
||||||
"reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
|
"reference": "d5a341b3fb61f3001970940afb1d332968a183ed",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^7.4 || ^8.0"
|
"php": "^7.4 || ^8.0"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"phpstan/phpstan": "<1.11.10"
|
"phpstan/phpstan": "<2.2.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpstan/phpstan": "^1.12 || ^2",
|
"phpstan/phpstan": "^2",
|
||||||
"phpstan/phpstan-strict-rules": "^1 || ^2",
|
"phpstan/phpstan-deprecation-rules": "^2",
|
||||||
"phpunit/phpunit": "^8 || ^9"
|
"phpstan/phpstan-strict-rules": "^2",
|
||||||
|
"phpunit/phpunit": "^9"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
@@ -675,7 +677,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/composer/pcre/issues",
|
"issues": "https://github.com/composer/pcre/issues",
|
||||||
"source": "https://github.com/composer/pcre/tree/3.3.2"
|
"source": "https://github.com/composer/pcre/tree/3.4.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -685,13 +687,9 @@
|
|||||||
{
|
{
|
||||||
"url": "https://github.com/composer",
|
"url": "https://github.com/composer",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/composer/composer",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-11-12T16:29:46+00:00"
|
"time": "2026-06-07T11:47:49+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "container-interop/container-interop",
|
"name": "container-interop/container-interop",
|
||||||
@@ -2314,26 +2312,25 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "enshrined/svg-sanitize",
|
"name": "enshrined/svg-sanitize",
|
||||||
"version": "0.16.0",
|
"version": "0.22.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/darylldoyle/svg-sanitizer.git",
|
"url": "https://github.com/darylldoyle/svg-sanitizer.git",
|
||||||
"reference": "239e257605e2141265b429e40987b2ee51bba4b4"
|
"reference": "0afa95ea74be155a7bcd6c6fb60c276c39984500"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/239e257605e2141265b429e40987b2ee51bba4b4",
|
"url": "https://api.github.com/repos/darylldoyle/svg-sanitizer/zipball/0afa95ea74be155a7bcd6c6fb60c276c39984500",
|
||||||
"reference": "239e257605e2141265b429e40987b2ee51bba4b4",
|
"reference": "0afa95ea74be155a7bcd6c6fb60c276c39984500",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-libxml": "*",
|
"ext-libxml": "*",
|
||||||
"ezyang/htmlpurifier": "^4.16",
|
"php": "^7.1 || ^8.0"
|
||||||
"php": "^5.6 || ^7.0 || ^8.0"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^5.7 || ^6.5 || ^8.5"
|
"phpunit/phpunit": "^6.5 || ^8.5"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -2354,9 +2351,9 @@
|
|||||||
"description": "An SVG sanitizer for PHP",
|
"description": "An SVG sanitizer for PHP",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/darylldoyle/svg-sanitizer/issues",
|
"issues": "https://github.com/darylldoyle/svg-sanitizer/issues",
|
||||||
"source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.16.0"
|
"source": "https://github.com/darylldoyle/svg-sanitizer/tree/0.22.0"
|
||||||
},
|
},
|
||||||
"time": "2023-03-20T10:51:12+00:00"
|
"time": "2025-08-12T10:13:48+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "essence/essence",
|
"name": "essence/essence",
|
||||||
@@ -2588,12 +2585,12 @@
|
|||||||
"version": "v5.5.1",
|
"version": "v5.5.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/firebase/php-jwt.git",
|
"url": "https://github.com/googleapis/php-jwt.git",
|
||||||
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6"
|
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/firebase/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6",
|
"url": "https://api.github.com/repos/googleapis/php-jwt/zipball/83b609028194aa042ea33b5af2d41a7427de80e6",
|
||||||
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6",
|
"reference": "83b609028194aa042ea33b5af2d41a7427de80e6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
@@ -2635,8 +2632,8 @@
|
|||||||
"php"
|
"php"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/firebase/php-jwt/issues",
|
"issues": "https://github.com/googleapis/php-jwt/issues",
|
||||||
"source": "https://github.com/firebase/php-jwt/tree/v5.5.1"
|
"source": "https://github.com/googleapis/php-jwt/tree/v5.5.1"
|
||||||
},
|
},
|
||||||
"time": "2021-11-08T20:18:51+00:00"
|
"time": "2021-11-08T20:18:51+00:00"
|
||||||
},
|
},
|
||||||
@@ -4973,27 +4970,27 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "onelogin/php-saml",
|
"name": "onelogin/php-saml",
|
||||||
"version": "3.4.1",
|
"version": "3.8.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/onelogin/php-saml.git",
|
"url": "https://github.com/SAML-Toolkits/php-saml.git",
|
||||||
"reference": "5fbf3486704ac9835b68184023ab54862c95f213"
|
"reference": "8732729ea91d1764a8d39062647845bba4bb3c29"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/onelogin/php-saml/zipball/5fbf3486704ac9835b68184023ab54862c95f213",
|
"url": "https://api.github.com/repos/SAML-Toolkits/php-saml/zipball/8732729ea91d1764a8d39062647845bba4bb3c29",
|
||||||
"reference": "5fbf3486704ac9835b68184023ab54862c95f213",
|
"reference": "8732729ea91d1764a8d39062647845bba4bb3c29",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": ">=5.4",
|
"php": ">=7.2",
|
||||||
"robrichards/xmlseclibs": ">=3.0.4"
|
"robrichards/xmlseclibs": "^3.1.5"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"pdepend/pdepend": "^2.5.0",
|
"pdepend/pdepend": "^2.5.0",
|
||||||
"php-coveralls/php-coveralls": "^1.0.2 || ^2.0",
|
"php-coveralls/php-coveralls": "^1.0.2 || ^2.0",
|
||||||
"phploc/phploc": "^2.1 || ^3.0 || ^4.0",
|
"phploc/phploc": "^2.1 || ^3.0 || ^4.0",
|
||||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1",
|
"phpunit/phpunit": ">8.5.51",
|
||||||
"sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0",
|
"sebastian/phpcpd": "^2.0 || ^3.0 || ^4.0",
|
||||||
"squizlabs/php_codesniffer": "^3.1.1"
|
"squizlabs/php_codesniffer": "^3.1.1"
|
||||||
},
|
},
|
||||||
@@ -5012,19 +5009,27 @@
|
|||||||
"license": [
|
"license": [
|
||||||
"MIT"
|
"MIT"
|
||||||
],
|
],
|
||||||
"description": "OneLogin PHP SAML Toolkit",
|
"description": "PHP SAML Toolkit",
|
||||||
"homepage": "https://developers.onelogin.com/saml/php",
|
"homepage": "https://github.com/SAML-Toolkits/php-saml",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
"Federation",
|
||||||
"SAML2",
|
"SAML2",
|
||||||
"onelogin",
|
"SSO",
|
||||||
|
"identity",
|
||||||
"saml"
|
"saml"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"email": "sixto.garcia@onelogin.com",
|
"email": "sixto.martin.garcia@gmail.com",
|
||||||
"issues": "https://github.com/onelogin/php-saml/issues",
|
"issues": "https://github.com/onelogin/SAML-Toolkits/issues",
|
||||||
"source": "https://github.com/onelogin/php-saml/"
|
"source": "https://github.com/onelogin/SAML-Toolkits/"
|
||||||
},
|
},
|
||||||
"time": "2019-11-25T17:30:07+00:00"
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/SAML-Toolkits",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-05-11T22:19:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "packbackbooks/lti-1p3-tool",
|
"name": "packbackbooks/lti-1p3-tool",
|
||||||
@@ -6465,16 +6470,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoffice/phpspreadsheet",
|
"name": "phpoffice/phpspreadsheet",
|
||||||
"version": "1.30.2",
|
"version": "1.30.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
"url": "https://github.com/PHPOffice/PhpSpreadsheet.git",
|
||||||
"reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2"
|
"reference": "97bcabd32a64924688487dcd64aceaf158affb5c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
|
"url": "https://api.github.com/repos/PHPOffice/PhpSpreadsheet/zipball/97bcabd32a64924688487dcd64aceaf158affb5c",
|
||||||
"reference": "09cdde5e2f078b9a3358dd217e2c8cb4dac84be2",
|
"reference": "97bcabd32a64924688487dcd64aceaf158affb5c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -6567,9 +6572,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
"issues": "https://github.com/PHPOffice/PhpSpreadsheet/issues",
|
||||||
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.2"
|
"source": "https://github.com/PHPOffice/PhpSpreadsheet/tree/1.30.5"
|
||||||
},
|
},
|
||||||
"time": "2026-01-11T05:58:24+00:00"
|
"time": "2026-05-31T05:13:11+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoffice/phpword",
|
"name": "phpoffice/phpword",
|
||||||
@@ -6685,16 +6690,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpseclib/phpseclib",
|
"name": "phpseclib/phpseclib",
|
||||||
"version": "2.0.52",
|
"version": "2.0.55",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpseclib/phpseclib.git",
|
"url": "https://github.com/phpseclib/phpseclib.git",
|
||||||
"reference": "2552c4001631d1cc844332faea6a08a49c964b28"
|
"reference": "d73c9e019a895be83b18a2ccccfa7e2b0a648743"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/2552c4001631d1cc844332faea6a08a49c964b28",
|
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/d73c9e019a895be83b18a2ccccfa7e2b0a648743",
|
||||||
"reference": "2552c4001631d1cc844332faea6a08a49c964b28",
|
"reference": "d73c9e019a895be83b18a2ccccfa7e2b0a648743",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -6775,7 +6780,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
"issues": "https://github.com/phpseclib/phpseclib/issues",
|
||||||
"source": "https://github.com/phpseclib/phpseclib/tree/2.0.52"
|
"source": "https://github.com/phpseclib/phpseclib/tree/2.0.55"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -6791,7 +6796,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-03-19T02:54:44+00:00"
|
"time": "2026-06-14T19:53:12+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/cache",
|
"name": "psr/cache",
|
||||||
@@ -7297,16 +7302,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "rmccue/requests",
|
"name": "rmccue/requests",
|
||||||
"version": "v2.0.17",
|
"version": "v2.0.18",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/WordPress/Requests.git",
|
"url": "https://github.com/WordPress/Requests.git",
|
||||||
"reference": "74d1648cc34e16a42ea25d548fc73ec107a90421"
|
"reference": "2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/WordPress/Requests/zipball/74d1648cc34e16a42ea25d548fc73ec107a90421",
|
"url": "https://api.github.com/repos/WordPress/Requests/zipball/2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773",
|
||||||
"reference": "74d1648cc34e16a42ea25d548fc73ec107a90421",
|
"reference": "2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -7379,20 +7384,20 @@
|
|||||||
"issues": "https://github.com/WordPress/Requests/issues",
|
"issues": "https://github.com/WordPress/Requests/issues",
|
||||||
"source": "https://github.com/WordPress/Requests"
|
"source": "https://github.com/WordPress/Requests"
|
||||||
},
|
},
|
||||||
"time": "2025-12-12T17:47:19+00:00"
|
"time": "2026-04-30T00:41:23+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "robrichards/xmlseclibs",
|
"name": "robrichards/xmlseclibs",
|
||||||
"version": "3.0.4",
|
"version": "3.1.5",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/robrichards/xmlseclibs.git",
|
"url": "https://github.com/robrichards/xmlseclibs.git",
|
||||||
"reference": "0a53d3c3aa87564910cae4ed01416441d3ae0db5"
|
"reference": "03062be78178cbb5e8f605cd255dc32a14981f92"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/0a53d3c3aa87564910cae4ed01416441d3ae0db5",
|
"url": "https://api.github.com/repos/robrichards/xmlseclibs/zipball/03062be78178cbb5e8f605cd255dc32a14981f92",
|
||||||
"reference": "0a53d3c3aa87564910cae4ed01416441d3ae0db5",
|
"reference": "03062be78178cbb5e8f605cd255dc32a14981f92",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -7419,9 +7424,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/robrichards/xmlseclibs/issues",
|
"issues": "https://github.com/robrichards/xmlseclibs/issues",
|
||||||
"source": "https://github.com/robrichards/xmlseclibs/tree/master"
|
"source": "https://github.com/robrichards/xmlseclibs/tree/3.1.5"
|
||||||
},
|
},
|
||||||
"time": "2019-11-05T11:44:22+00:00"
|
"time": "2026-03-13T10:31:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sabre/vobject",
|
"name": "sabre/vobject",
|
||||||
@@ -7496,16 +7501,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "setasign/fpdi",
|
"name": "setasign/fpdi",
|
||||||
"version": "v2.6.6",
|
"version": "v2.6.8",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Setasign/FPDI.git",
|
"url": "https://github.com/Setasign/FPDI.git",
|
||||||
"reference": "de0cf35911be3e9ea63b48e0f307883b1c7c48ac"
|
"reference": "881945be29a4996ad3d008eb18ddc01fa3df890c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/de0cf35911be3e9ea63b48e0f307883b1c7c48ac",
|
"url": "https://api.github.com/repos/Setasign/FPDI/zipball/881945be29a4996ad3d008eb18ddc01fa3df890c",
|
||||||
"reference": "de0cf35911be3e9ea63b48e0f307883b1c7c48ac",
|
"reference": "881945be29a4996ad3d008eb18ddc01fa3df890c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -7517,7 +7522,7 @@
|
|||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^8.5.52",
|
"phpunit/phpunit": "^8.5.52",
|
||||||
"setasign/fpdf": "~1.8.6",
|
"setasign/fpdf": "^1.9.0",
|
||||||
"setasign/tfpdf": "~1.33",
|
"setasign/tfpdf": "~1.33",
|
||||||
"squizlabs/php_codesniffer": "^3.5",
|
"squizlabs/php_codesniffer": "^3.5",
|
||||||
"tecnickcom/tcpdf": "^6.8"
|
"tecnickcom/tcpdf": "^6.8"
|
||||||
@@ -7556,7 +7561,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/Setasign/FPDI/issues",
|
"issues": "https://github.com/Setasign/FPDI/issues",
|
||||||
"source": "https://github.com/Setasign/FPDI/tree/v2.6.6"
|
"source": "https://github.com/Setasign/FPDI/tree/v2.6.8"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -7564,7 +7569,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-03-13T08:38:20+00:00"
|
"time": "2026-06-11T10:37:24+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sonata-project/admin-bundle",
|
"name": "sonata-project/admin-bundle",
|
||||||
@@ -8387,28 +8392,28 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "stripe/stripe-php",
|
"name": "stripe/stripe-php",
|
||||||
"version": "v19.4.1",
|
"version": "v20.2.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/stripe/stripe-php.git",
|
"url": "https://github.com/stripe/stripe-php.git",
|
||||||
"reference": "095384404587d07de2ad1154c389c4051c5ed92f"
|
"reference": "c628cfa0b3de4ef5110b2c2bfbf881a33a52fdd5"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/095384404587d07de2ad1154c389c4051c5ed92f",
|
"url": "https://api.github.com/repos/stripe/stripe-php/zipball/c628cfa0b3de4ef5110b2c2bfbf881a33a52fdd5",
|
||||||
"reference": "095384404587d07de2ad1154c389c4051c5ed92f",
|
"reference": "c628cfa0b3de4ef5110b2c2bfbf881a33a52fdd5",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-curl": "*",
|
"ext-curl": "*",
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"php": ">=5.6.0"
|
"php": ">=7.2.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"friendsofphp/php-cs-fixer": "3.94.0",
|
"friendsofphp/php-cs-fixer": "3.94.0",
|
||||||
"phpstan/phpstan": "^1.2",
|
"phpstan/phpstan": "^1.2",
|
||||||
"phpunit/phpunit": "^5.7 || ^9.0"
|
"phpunit/phpunit": "^8.0 || ^9.0"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
@@ -8417,6 +8422,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"lib/version_check.php"
|
||||||
|
],
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Stripe\\": "lib/"
|
"Stripe\\": "lib/"
|
||||||
}
|
}
|
||||||
@@ -8440,22 +8448,22 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/stripe/stripe-php/issues",
|
"issues": "https://github.com/stripe/stripe-php/issues",
|
||||||
"source": "https://github.com/stripe/stripe-php/tree/v19.4.1"
|
"source": "https://github.com/stripe/stripe-php/tree/v20.2.1"
|
||||||
},
|
},
|
||||||
"time": "2026-03-06T22:53:13+00:00"
|
"time": "2026-06-12T22:41:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "studio-42/elfinder",
|
"name": "studio-42/elfinder",
|
||||||
"version": "2.1.66",
|
"version": "2.1.69",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Studio-42/elFinder.git",
|
"url": "https://github.com/Studio-42/elFinder.git",
|
||||||
"reference": "78488951e44d69e8b9e4e849f8268df408632a6c"
|
"reference": "8f2c3ffafcdd52cf4515f1eec172f4eee44552ad"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/Studio-42/elFinder/zipball/78488951e44d69e8b9e4e849f8268df408632a6c",
|
"url": "https://api.github.com/repos/Studio-42/elFinder/zipball/8f2c3ffafcdd52cf4515f1eec172f4eee44552ad",
|
||||||
"reference": "78488951e44d69e8b9e4e849f8268df408632a6c",
|
"reference": "8f2c3ffafcdd52cf4515f1eec172f4eee44552ad",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -8502,7 +8510,7 @@
|
|||||||
"homepage": "http://elfinder.org",
|
"homepage": "http://elfinder.org",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/Studio-42/elFinder/issues",
|
"issues": "https://github.com/Studio-42/elFinder/issues",
|
||||||
"source": "https://github.com/Studio-42/elFinder/tree/2.1.66"
|
"source": "https://github.com/Studio-42/elFinder/tree/2.1.69"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -8510,7 +8518,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-08-28T11:51:22+00:00"
|
"time": "2026-05-07T12:53:30+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sunra/php-simple-html-dom-parser",
|
"name": "sunra/php-simple-html-dom-parser",
|
||||||
@@ -9299,6 +9307,71 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-07-20T09:59:04+00:00"
|
"time": "2022-07-20T09:59:04+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/css-selector",
|
||||||
|
"version": "v3.4.47",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/css-selector.git",
|
||||||
|
"reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/css-selector/zipball/da3d9da2ce0026771f5fe64cb332158f1bd2bc33",
|
||||||
|
"reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^5.5.9|>=7.0.8"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Component\\CssSelector\\": ""
|
||||||
|
},
|
||||||
|
"exclude-from-classmap": [
|
||||||
|
"/Tests/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Fabien Potencier",
|
||||||
|
"email": "fabien@symfony.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Jean-François Simon",
|
||||||
|
"email": "jeanfrancois.simon@sensiolabs.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony CssSelector Component",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/css-selector/tree/v3.4.47"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2020-10-24T10:57:07+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/debug",
|
"name": "symfony/debug",
|
||||||
"version": "v4.4.44",
|
"version": "v4.4.44",
|
||||||
@@ -10439,7 +10512,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-apcu",
|
"name": "symfony/polyfill-apcu",
|
||||||
"version": "v1.33.0",
|
"version": "v1.37.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-apcu.git",
|
"url": "https://github.com/symfony/polyfill-apcu.git",
|
||||||
@@ -10493,7 +10566,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-apcu/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-apcu/tree/v1.37.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10517,16 +10590,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-ctype",
|
"name": "symfony/polyfill-ctype",
|
||||||
"version": "v1.33.0",
|
"version": "v1.37.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-ctype.git",
|
"url": "https://github.com/symfony/polyfill-ctype.git",
|
||||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
|
"reference": "141046a8f9477948ff284fa65be2095baafb94f2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
|
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/141046a8f9477948ff284fa65be2095baafb94f2",
|
||||||
"reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
|
"reference": "141046a8f9477948ff284fa65be2095baafb94f2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -10576,7 +10649,7 @@
|
|||||||
"portable"
|
"portable"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.37.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10596,20 +10669,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-09-09T11:45:10+00:00"
|
"time": "2026-04-10T16:19:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-iconv",
|
"name": "symfony/polyfill-iconv",
|
||||||
"version": "v1.33.0",
|
"version": "v1.37.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-iconv.git",
|
"url": "https://github.com/symfony/polyfill-iconv.git",
|
||||||
"reference": "5f3b930437ae03ae5dff61269024d8ea1b3774aa"
|
"reference": "2c5729fd241b4b22f6e4b436bc3354a4f262df57"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/5f3b930437ae03ae5dff61269024d8ea1b3774aa",
|
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/2c5729fd241b4b22f6e4b436bc3354a4f262df57",
|
||||||
"reference": "5f3b930437ae03ae5dff61269024d8ea1b3774aa",
|
"reference": "2c5729fd241b4b22f6e4b436bc3354a4f262df57",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -10660,7 +10733,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-iconv/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-iconv/tree/v1.37.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10680,20 +10753,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-09-17T14:58:18+00:00"
|
"time": "2026-04-10T16:19:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-icu",
|
"name": "symfony/polyfill-intl-icu",
|
||||||
"version": "v1.33.0",
|
"version": "v1.38.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-intl-icu.git",
|
"url": "https://github.com/symfony/polyfill-intl-icu.git",
|
||||||
"reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c"
|
"reference": "445c90e341fccda10311019cf82ff73bb7343945"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c",
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/445c90e341fccda10311019cf82ff73bb7343945",
|
||||||
"reference": "bfc8fa13dbaf21d69114b0efcd72ab700fb04d0c",
|
"reference": "445c90e341fccda10311019cf82ff73bb7343945",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -10748,7 +10821,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-intl-icu/tree/v1.38.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10768,20 +10841,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-06-20T22:24:30+00:00"
|
"time": "2026-05-25T11:52:53+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-idn",
|
"name": "symfony/polyfill-intl-idn",
|
||||||
"version": "v1.33.0",
|
"version": "v1.38.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
"url": "https://github.com/symfony/polyfill-intl-idn.git",
|
||||||
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3"
|
"reference": "dc21118016c039a66235cf93d96b435ffb282412"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/9614ac4d8061dc257ecc64cba1b140873dce8ad3",
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412",
|
||||||
"reference": "9614ac4d8061dc257ecc64cba1b140873dce8ad3",
|
"reference": "dc21118016c039a66235cf93d96b435ffb282412",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -10835,7 +10908,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10855,20 +10928,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-09-10T14:38:51+00:00"
|
"time": "2026-05-25T15:22:23+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-intl-normalizer",
|
"name": "symfony/polyfill-intl-normalizer",
|
||||||
"version": "v1.33.0",
|
"version": "v1.38.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
|
||||||
"reference": "3833d7255cc303546435cb650316bff708a1c75c"
|
"reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c",
|
"url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b",
|
||||||
"reference": "3833d7255cc303546435cb650316bff708a1c75c",
|
"reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -10920,7 +10993,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -10940,20 +11013,20 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-09-09T11:45:10+00:00"
|
"time": "2026-05-25T13:48:31+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-mbstring",
|
"name": "symfony/polyfill-mbstring",
|
||||||
"version": "v1.33.0",
|
"version": "v1.38.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493"
|
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
|
||||||
"reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493",
|
"reference": "d3d318bad5e7a1bfbd026009c8bfb8d8f99ae6b6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -11005,7 +11078,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -11025,7 +11098,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-12-23T08:48:59+00:00"
|
"time": "2026-05-27T06:59:30+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php56",
|
"name": "symfony/polyfill-php56",
|
||||||
@@ -11165,16 +11238,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php80",
|
"name": "symfony/polyfill-php80",
|
||||||
"version": "v1.33.0",
|
"version": "v1.37.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/symfony/polyfill-php80.git",
|
"url": "https://github.com/symfony/polyfill-php80.git",
|
||||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608"
|
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||||
"reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608",
|
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -11225,7 +11298,7 @@
|
|||||||
"shim"
|
"shim"
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0"
|
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -11245,7 +11318,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2025-01-02T08:10:11+00:00"
|
"time": "2026-04-10T16:19:22+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/process",
|
"name": "symfony/process",
|
||||||
@@ -15734,71 +15807,6 @@
|
|||||||
],
|
],
|
||||||
"time": "2022-07-25T12:56:14+00:00"
|
"time": "2022-07-25T12:56:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "symfony/css-selector",
|
|
||||||
"version": "v3.4.47",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/symfony/css-selector.git",
|
|
||||||
"reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/da3d9da2ce0026771f5fe64cb332158f1bd2bc33",
|
|
||||||
"reference": "da3d9da2ce0026771f5fe64cb332158f1bd2bc33",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^5.5.9|>=7.0.8"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Symfony\\Component\\CssSelector\\": ""
|
|
||||||
},
|
|
||||||
"exclude-from-classmap": [
|
|
||||||
"/Tests/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Fabien Potencier",
|
|
||||||
"email": "fabien@symfony.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Jean-François Simon",
|
|
||||||
"email": "jeanfrancois.simon@sensiolabs.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Symfony Community",
|
|
||||||
"homepage": "https://symfony.com/contributors"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Symfony CssSelector Component",
|
|
||||||
"homepage": "https://symfony.com",
|
|
||||||
"support": {
|
|
||||||
"source": "https://github.com/symfony/css-selector/tree/v3.4.47"
|
|
||||||
},
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://symfony.com/sponsor",
|
|
||||||
"type": "custom"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/fabpot",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
|
||||||
"type": "tidelift"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2020-10-24T10:57:07+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "theseer/tokenizer",
|
"name": "theseer/tokenizer",
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
@@ -15869,11 +15877,11 @@
|
|||||||
"ddeboer/data-import": 0,
|
"ddeboer/data-import": 0,
|
||||||
"doctrine/data-fixtures": 20,
|
"doctrine/data-fixtures": 20,
|
||||||
"doctrine/migrations": 20,
|
"doctrine/migrations": 20,
|
||||||
|
"packbackbooks/lti-1p3-tool": 20,
|
||||||
"php-xapi/client": 20,
|
"php-xapi/client": 20,
|
||||||
"php-xapi/repository-api": 20,
|
"php-xapi/repository-api": 20,
|
||||||
"php-xapi/repository-doctrine": 20,
|
"php-xapi/repository-doctrine": 20,
|
||||||
"yuloh/bccomp-polyfill": 20,
|
"yuloh/bccomp-polyfill": 20
|
||||||
"packbackbooks/lti-1p3-tool": 20
|
|
||||||
},
|
},
|
||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
@@ -15891,6 +15899,6 @@
|
|||||||
"ext-zip": "*",
|
"ext-zip": "*",
|
||||||
"ext-zlib": "*"
|
"ext-zlib": "*"
|
||||||
},
|
},
|
||||||
"platform-dev": [],
|
"platform-dev": {},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,63 @@
|
|||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="version" aria-label="1.11.40">
|
||||||
|
<a id="1.11.40"></a>
|
||||||
|
<h1>Chamilo 1.11.40 - Quimper, ?/06/2026</h1>
|
||||||
|
<h3>Release notes - summary</h3>
|
||||||
|
<p>Chamilo 1.11.40 is a security and bugfix release on top of 1.11.38. For any significant change, please check the 1.11.30 release notes.</p>
|
||||||
|
<h3>Release name</h3>
|
||||||
|
<p><a href="https://en.wikipedia.org/wiki/Quimper">Quimper</a> is the prefecture of the Finistère department in Brittany, north-western France. It is known for its Gothic cathedral, well-preserved medieval old town, and its centuries-old tradition of hand-painted Quimper faïence pottery.</p>
|
||||||
|
<h3>Security fixes</h3>
|
||||||
|
<ul aria-live="off">
|
||||||
|
<li>[2026-05-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/064787b28633b00218a6bf365248fdbf07b00e79">064787b2</a> - <a href="https://github.com/chamilo/chamilo-lms/security/advisories/GHSA-wjxh-pvwh-h84p">GHSA-wjxh-pvwh-h84p</a>) Security: LP: Using current user ID instead of request uid</li>
|
||||||
|
<li>[2026-04-29] (<a href="https://github.com/chamilo/chamilo-lms/commit/c497f7ef7fadeabb80ba27046a3ad5ce0edfc139">c497f7ef</a> - <a href="https://github.com/chamilo/chamilo-lms/security/advisories/GHSA-8rj2-88v5-mx6w">GHSA-8rj2-88v5-mx6w</a>) Security: Remove <code>eval</code> usage in <code>HTML_QuickForm</code> library and replace with safer array operations</li>
|
||||||
|
<li>[2026-04-24] (<a href="https://github.com/chamilo/chamilo-lms/commit/7cc961d60ac27eaccd6bcc8db201de2c65adfdf3">7cc961d6</a> - <a href="https://github.com/chamilo/chamilo-lms/security/advisories/GHSA-prqp-8x29-cr88">GHSA-prqp-8x29-cr88</a>) Security: Validate and enforce <code>groupOp</code> keyword in AJAX filters to prevent improper usage or invalid values</li>
|
||||||
|
<li>[2026-03-31] (<a href="https://github.com/chamilo/chamilo-lms/commit/823ed10b983981cdaf0a44f79041757e08ffb3c1">823ed10b</a> - <a href="https://github.com/chamilo/chamilo-lms/security/advisories/GHSA-gcjp-f7jm-rrrg">GHSA-gcjp-f7jm-rrrg</a>) Security: Fix stored XSS via null-byte bypass in user registration</li>
|
||||||
|
<li>[2026-03-27] (<a href="https://github.com/chamilo/chamilo-lms/commit/08b4fbf3e8ec957a982805997c080762394d714b">08b4fbf3</a> - <a href="https://github.com/chamilo/chamilo-lms/security/advisories/GHSA-4hwq-pv7c-3928">GHSA-4hwq-pv7c-3928</a>) Security: Improve languages and sublanguages scripts security</li>
|
||||||
|
<li>[2026-03-24] (<a href="https://github.com/chamilo/chamilo-lms/commit/836c5c9ad6fb4ccf40f8c0c015e1bb38a81f91c8">836c5c9a</a>) Security: Fix multiple filtering issues about session_lifetime</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Notable new Features</h3>
|
||||||
|
<h4>For end-users, teachers and Chamilo admins</h4>
|
||||||
|
These features are immediately available to users through the web interface.<br />
|
||||||
|
<ul aria-live="off">
|
||||||
|
<li>No notable new feature</li>
|
||||||
|
</ul>
|
||||||
|
<h4>For developers and sysadmins</h4>
|
||||||
|
Although most features here will be used by teachers or Chamilo admins, they require sysadmin privileges to enable them on the server.
|
||||||
|
<ul aria-live="off">
|
||||||
|
<li>[2026-04-14] (<a href="https://github.com/chamilo/chamilo-lms/commit/7e081bbf88524dea854c5699899c1aa69acbb7d4">7e081bbf</a> - <a href="https://task.beeznest.com/issues/23138">BT#23138</a>) Webservice: Add <code>delete_session</code> method in API</li>
|
||||||
|
<li>[2026-04-14] (<a href="https://github.com/chamilo/chamilo-lms/commit/8660b53d3eb529a1a42d4c3caaad625462dd5ea2">8660b53d</a> - <a href="https://task.beeznest.com/issues/23138">BT#23138</a>) Webservice: Add <code>update_course</code> method in API</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Improvements (minor features) and debug</h3>
|
||||||
|
In reverse chronological order...
|
||||||
|
<ul aria-live="off">
|
||||||
|
<li>[2026-05-18] (<a href="https://github.com/chamilo/chamilo-lms/commit/61d29b7da232cd59f43c1fa16cca4372e67f5073">61d29b7d</a> - <a href="https://task.beeznest.com/issues/23439">BT#23439</a>) User: Internal: fix user update when imported user already exist and status with value user or teacher</li>
|
||||||
|
<li>[2026-04-18] (<a href="https://github.com/chamilo/chamilo-lms/commit/729353b329a95b4e6b1eca393d8907a8df262058">729353b3</a> - <a href="https://task.beeznest.com/issues/23282">BT#23282</a>) Admin: Fix weird array-reference issue in user_move_stats.php</li>
|
||||||
|
<li>[2026-04-17] (<a href="https://github.com/chamilo/chamilo-lms/commit/5b555a74357e3a96803e99c7ba5005001b38ccf6">5b555a74</a> - <a href="https://task.beeznest.com/issues/23282">BT#23282</a>) Tracking: Fix processUserDataMove() for main/admin/user_move_stats.php if some data are empty</li>
|
||||||
|
<li>[2026-04-16] (<a href="https://github.com/chamilo/chamilo-lms/commit/389ce3b62012ace163c28d91b68f6a4f09ea5aaa">389ce3b6</a>) Course: Set Moodle display defaults for PDFs and links</li>
|
||||||
|
<li>[2026-04-14] (<a href="https://github.com/chamilo/chamilo-lms/commit/9531ba05f88f119ed9f4d0b593a07c97c217b78a">9531ba05</a> - <a href="https://task.beeznest.com/issues/23208">BT#23208</a>) Group: Fix group ordering to be the same for admin, teacher and student</li>
|
||||||
|
<li>[2026-03-25] (<a href="https://github.com/chamilo/chamilo-lms/commit/b8b9c76bd88ad297c3a0e669a84fac64a0646b50">b8b9c76b</a>) Vendor: Upgrade <code>enshrined/svg-sanitize</code> to ^0.22.0</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Stylesheets and theming</h3>
|
||||||
|
<ul aria-live="off">
|
||||||
|
<li>No notable style change</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Web services</h3>
|
||||||
|
<ul aria-live="off">
|
||||||
|
<li>See new <code>delete_session</code> and <code>update_course</code> API methods in notable new features above</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Removals</h3>
|
||||||
|
<ul aria-live="off">
|
||||||
|
<li>No notable removal</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Known issues</h3>
|
||||||
|
<ul aria-live="off">
|
||||||
|
<li>(<a href="https://github.com/chamilo/chamilo-lms/issues/5910">GH#5910</a>, <a href="https://github.com/chamilo/chamilo-lms/issues/7354">GH#7354</a>, <a href="https://github.com/chamilo/chamilo-lms/issues/7676">GH#7676</a>) PDF certificate generation may fail to export embedded images in some configurations</li>
|
||||||
|
<li>(<a href="https://github.com/chamilo/chamilo-lms/issues/8153">GH#8153</a>, <a href="https://github.com/chamilo/chamilo-lms/issues/8406">GH#8406</a>) Learning paths may be invisible to students in some PHP 8.3 configurations despite being set to public</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="version" aria-label="1.11.38">
|
<div class="version" aria-label="1.11.38">
|
||||||
<a id="1.11.38"></a>
|
<a id="1.11.38"></a>
|
||||||
<h1>Chamilo 1.11.38 - Pontorson, 23/03/2026</h1>
|
<h1>Chamilo 1.11.38 - Pontorson, 23/03/2026</h1>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ echo '</div>';
|
|||||||
|
|
||||||
api_display_tool_title($tool_name);
|
api_display_tool_title($tool_name);
|
||||||
|
|
||||||
if ($_POST['form_sent']) {
|
if (!empty($_POST['form_sent']) && Security::check_token('post')) {
|
||||||
$form_sent = $_POST['form_sent'];
|
$form_sent = $_POST['form_sent'];
|
||||||
$users = is_array($_POST['user_list']) ? $_POST['user_list'] : [];
|
$users = is_array($_POST['user_list']) ? $_POST['user_list'] : [];
|
||||||
$url_list = is_array($_POST['url_list']) ? $_POST['url_list'] : [];
|
$url_list = is_array($_POST['url_list']) ? $_POST['url_list'] : [];
|
||||||
@@ -79,7 +79,7 @@ $first_letter_user_lower = Database::escape_string(api_strtolower($first_letter_
|
|||||||
$target_name = api_sort_by_first_name() ? 'firstname' : 'lastname';
|
$target_name = api_sort_by_first_name() ? 'firstname' : 'lastname';
|
||||||
$target_name = 'lastname';
|
$target_name = 'lastname';
|
||||||
$sql = "SELECT user_id,lastname,firstname,username FROM $tbl_user
|
$sql = "SELECT user_id,lastname,firstname,username FROM $tbl_user
|
||||||
WHERE ".$target_name." LIKE '".$first_letter_user_lower."%' OR ".$target_name." LIKE '".$first_letter_user_lower."%'
|
WHERE ".$target_name." LIKE '".$first_letter_user_lower."%'
|
||||||
ORDER BY ".(count($users) > 0 ? "(user_id IN(".implode(',', $users).")) DESC," : "")." ".$target_name;
|
ORDER BY ".(count($users) > 0 ? "(user_id IN(".implode(',', $users).")) DESC," : "")." ".$target_name;
|
||||||
$result = Database::query($sql);
|
$result = Database::query($sql);
|
||||||
$db_users = Database::store_result($result);
|
$db_users = Database::store_result($result);
|
||||||
@@ -93,6 +93,7 @@ unset($result);
|
|||||||
|
|
||||||
<form name="formulaire" method="post" action="<?php echo api_get_self(); ?>" style="margin:0px;">
|
<form name="formulaire" method="post" action="<?php echo api_get_self(); ?>" style="margin:0px;">
|
||||||
<input type="hidden" name="form_sent" value="1"/>
|
<input type="hidden" name="form_sent" value="1"/>
|
||||||
|
<?php echo Security::get_HTML_token(); ?>
|
||||||
<table border="0" cellpadding="5" cellspacing="0" width="100%">
|
<table border="0" cellpadding="5" cellspacing="0" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td width="40%" align="center">
|
<td width="40%" align="center">
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ $errorMsg = '';
|
|||||||
$users = $sessions = [];
|
$users = $sessions = [];
|
||||||
$promotion = new Promotion();
|
$promotion = new Promotion();
|
||||||
$id = intval($_GET['id']);
|
$id = intval($_GET['id']);
|
||||||
if (isset($_POST['form_sent']) && $_POST['form_sent']) {
|
if (isset($_POST['form_sent']) && $_POST['form_sent'] && Security::check_token('post')) {
|
||||||
$form_sent = $_POST['form_sent'];
|
$form_sent = $_POST['form_sent'];
|
||||||
$session_in_promotion_posted = $_POST['session_in_promotion_name'];
|
$session_in_promotion_posted = $_POST['session_in_promotion_name'];
|
||||||
if (!is_array($session_in_promotion_posted)) {
|
if (!is_array($session_in_promotion_posted)) {
|
||||||
@@ -157,6 +157,7 @@ if ($add_type == 'multiple') {
|
|||||||
echo Display::input('hidden', 'id', $id);
|
echo Display::input('hidden', 'id', $id);
|
||||||
echo Display::input('hidden', 'form_sent', '1');
|
echo Display::input('hidden', 'form_sent', '1');
|
||||||
echo Display::input('hidden', 'add_type', null);
|
echo Display::input('hidden', 'add_type', null);
|
||||||
|
echo Security::get_HTML_token();
|
||||||
if (!empty($errorMsg)) {
|
if (!empty($errorMsg)) {
|
||||||
echo Display::return_message($errorMsg, 'normal'); //main API
|
echo Display::return_message($errorMsg, 'normal'); //main API
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,19 +26,68 @@ $course_list = CourseManager::get_courses_list(
|
|||||||
'',
|
'',
|
||||||
api_get_current_access_url_id()
|
api_get_current_access_url_id()
|
||||||
);
|
);
|
||||||
$formSent = null;
|
|
||||||
$courses = $selected_courses = [];
|
$courses = $selected_courses = [];
|
||||||
|
|
||||||
if (isset($_POST['formSent']) && $_POST['formSent']) {
|
$form = new FormValidator('export', 'post', api_get_self());
|
||||||
$formSent = $_POST['formSent'];
|
$form->addHeader($tool_name);
|
||||||
$select_type = (int) ($_POST['select_type']);
|
$form->addElement(
|
||||||
$file_type = $_POST['file_type'];
|
'radio',
|
||||||
$includeUsers = (empty($_POST['include_users']) ? false : true);
|
'select_type',
|
||||||
$includeExtraFields = (isset($_POST['include_extrafields']) && 1 === (int) $_POST['include_extrafields']);
|
get_lang('Option'),
|
||||||
|
get_lang('ExportAllCoursesList'),
|
||||||
|
'1',
|
||||||
|
['onclick' => "javascript: if(this.checked){document.getElementById('div-course-list').style.display='none';}"]
|
||||||
|
);
|
||||||
|
|
||||||
|
$form->addElement(
|
||||||
|
'radio',
|
||||||
|
'select_type',
|
||||||
|
'',
|
||||||
|
get_lang('ExportSelectedCoursesFromCoursesList'),
|
||||||
|
'2',
|
||||||
|
['onclick' => "javascript: if(this.checked){document.getElementById('div-course-list').style.display='block';}"]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($course_list)) {
|
||||||
|
$form->addHtml('<div id="div-course-list" style="display:none">');
|
||||||
|
$coursesInList = [];
|
||||||
|
foreach ($course_list as $course) {
|
||||||
|
$coursesInList[$course['code']] = $course['title'].' ('.$course['code'].')';
|
||||||
|
}
|
||||||
|
|
||||||
|
$form->addSelect(
|
||||||
|
'course_code',
|
||||||
|
get_lang('WhichCoursesToExport'),
|
||||||
|
$coursesInList,
|
||||||
|
['multiple' => 'multiple']
|
||||||
|
);
|
||||||
|
|
||||||
|
$form->addHtml('</div>');
|
||||||
|
}
|
||||||
|
|
||||||
|
$form->addElement('radio', 'file_type', get_lang('OutputFileType'), 'CSV', 'csv', null);
|
||||||
|
$form->addElement('radio', 'file_type', '', 'XLS', 'xls', null);
|
||||||
|
$form->addElement('radio', 'file_type', null, 'XML', 'xml', null, ['id' => 'file_type_xml']);
|
||||||
|
|
||||||
|
$form->addElement('checkbox', 'include_users', get_lang('ExportUsers'), '', '1');
|
||||||
|
|
||||||
|
$form->addElement('checkbox', 'include_extrafields', get_lang('ExportExtraFields'), '', '1');
|
||||||
|
|
||||||
|
$form->setDefaults(['select_type' => '1', 'file_type' => 'csv', 'include_users' => '1', 'include_extrafields' => 0]);
|
||||||
|
|
||||||
|
$form->addButtonExport(get_lang('ExportCourses'));
|
||||||
|
$form->protect();
|
||||||
|
|
||||||
|
if ($form->validate()) {
|
||||||
|
$values = $form->exportValues();
|
||||||
|
$select_type = (int) $values['select_type'];
|
||||||
|
$file_type = $values['file_type'];
|
||||||
|
$includeUsers = !empty($values['include_users']);
|
||||||
|
$includeExtraFields = isset($values['include_extrafields']) && 1 === (int) $values['include_extrafields'];
|
||||||
|
|
||||||
if (2 == $select_type) {
|
if (2 == $select_type) {
|
||||||
// Get selected courses from courses list in form sent
|
// Get selected courses from courses list in form sent
|
||||||
$selected_courses = $_POST['course_code'];
|
$selected_courses = $values['course_code'] ?? [];
|
||||||
if (is_array($selected_courses)) {
|
if (is_array($selected_courses)) {
|
||||||
foreach ($course_list as $course) {
|
foreach ($course_list as $course) {
|
||||||
if (!in_array($course['code'], $selected_courses)) {
|
if (!in_array($course['code'], $selected_courses)) {
|
||||||
@@ -156,55 +205,6 @@ if (isset($_POST['formSent']) && $_POST['formSent']) {
|
|||||||
|
|
||||||
Display::display_header($tool_name);
|
Display::display_header($tool_name);
|
||||||
|
|
||||||
$form = new FormValidator('export', 'post', api_get_self());
|
|
||||||
$form->addHeader($tool_name);
|
|
||||||
$form->addHidden('formSent', 1);
|
|
||||||
$form->addElement(
|
|
||||||
'radio',
|
|
||||||
'select_type',
|
|
||||||
get_lang('Option'),
|
|
||||||
get_lang('ExportAllCoursesList'),
|
|
||||||
'1',
|
|
||||||
['onclick' => "javascript: if(this.checked){document.getElementById('div-course-list').style.display='none';}"]
|
|
||||||
);
|
|
||||||
|
|
||||||
$form->addElement(
|
|
||||||
'radio',
|
|
||||||
'select_type',
|
|
||||||
'',
|
|
||||||
get_lang('ExportSelectedCoursesFromCoursesList'),
|
|
||||||
'2',
|
|
||||||
['onclick' => "javascript: if(this.checked){document.getElementById('div-course-list').style.display='block';}"]
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!empty($course_list)) {
|
|
||||||
$form->addHtml('<div id="div-course-list" style="display:none">');
|
|
||||||
$coursesInList = [];
|
|
||||||
foreach ($course_list as $course) {
|
|
||||||
$coursesInList[$course['code']] = $course['title'].' ('.$course['code'].')';
|
|
||||||
}
|
|
||||||
|
|
||||||
$form->addSelect(
|
|
||||||
'course_code',
|
|
||||||
get_lang('WhichCoursesToExport'),
|
|
||||||
$coursesInList,
|
|
||||||
['multiple' => 'multiple']
|
|
||||||
);
|
|
||||||
|
|
||||||
$form->addHtml('</div>');
|
|
||||||
}
|
|
||||||
|
|
||||||
$form->addElement('radio', 'file_type', get_lang('OutputFileType'), 'CSV', 'csv', null);
|
|
||||||
$form->addElement('radio', 'file_type', '', 'XLS', 'xls', null);
|
|
||||||
$form->addElement('radio', 'file_type', null, 'XML', 'xml', null, ['id' => 'file_type_xml']);
|
|
||||||
|
|
||||||
$form->addElement('checkbox', 'include_users', get_lang('ExportUsers'), '', '1');
|
|
||||||
|
|
||||||
$form->addElement('checkbox', 'include_extrafields', get_lang('ExportExtraFields'), '', '1');
|
|
||||||
|
|
||||||
$form->setDefaults(['select_type' => '1', 'file_type' => 'csv', 'include_users' => '1', 'include_extrafields' => 0]);
|
|
||||||
|
|
||||||
$form->addButtonExport(get_lang('ExportCourses'));
|
|
||||||
$form->display();
|
$form->display();
|
||||||
|
|
||||||
Display::display_footer();
|
Display::display_footer();
|
||||||
|
|||||||
@@ -215,9 +215,23 @@ $tool_name = get_lang('ImportCourses').' CSV';
|
|||||||
$interbreadcrumb[] = ['url' => 'index.php', 'name' => get_lang('PlatformAdmin')];
|
$interbreadcrumb[] = ['url' => 'index.php', 'name' => get_lang('PlatformAdmin')];
|
||||||
|
|
||||||
set_time_limit(0);
|
set_time_limit(0);
|
||||||
|
|
||||||
|
$form = new FormValidator(
|
||||||
|
'import',
|
||||||
|
'post',
|
||||||
|
api_get_self(),
|
||||||
|
null,
|
||||||
|
['enctype' => 'multipart/form-data']
|
||||||
|
);
|
||||||
|
$form->addHeader($tool_name);
|
||||||
|
$form->addElement('file', 'import_file', get_lang('ImportCSVFileLocation'));
|
||||||
|
$form->addElement('checkbox', 'add_me_as_teacher', null, get_lang('AddMeAsTeacherInCourses'));
|
||||||
|
$form->addButtonImport(get_lang('Import'), 'save');
|
||||||
|
$form->protect();
|
||||||
|
|
||||||
Display::display_header($tool_name);
|
Display::display_header($tool_name);
|
||||||
|
|
||||||
if (isset($_POST['formSent']) && $_POST['formSent']) {
|
if ($form->validate()) {
|
||||||
if (empty($_FILES['import_file']['tmp_name'])) {
|
if (empty($_FILES['import_file']['tmp_name'])) {
|
||||||
$error_message = get_lang('UplUploadFailed');
|
$error_message = get_lang('UplUploadFailed');
|
||||||
echo Display::return_message($error_message, 'error', false);
|
echo Display::return_message($error_message, 'error', false);
|
||||||
@@ -250,18 +264,6 @@ if (isset($errors) && count($errors) != 0) {
|
|||||||
echo Display::return_message($error_message, 'error', false);
|
echo Display::return_message($error_message, 'error', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
$form = new FormValidator(
|
|
||||||
'import',
|
|
||||||
'post',
|
|
||||||
api_get_self(),
|
|
||||||
null,
|
|
||||||
['enctype' => 'multipart/form-data']
|
|
||||||
);
|
|
||||||
$form->addHeader($tool_name);
|
|
||||||
$form->addElement('file', 'import_file', get_lang('ImportCSVFileLocation'));
|
|
||||||
$form->addElement('checkbox', 'add_me_as_teacher', null, get_lang('AddMeAsTeacherInCourses'));
|
|
||||||
$form->addButtonImport(get_lang('Import'), 'save');
|
|
||||||
$form->addElement('hidden', 'formSent', 1);
|
|
||||||
$form->display();
|
$form->display();
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ $errorMsg = $firstLetterCourse = '';
|
|||||||
$UserList = [];
|
$UserList = [];
|
||||||
|
|
||||||
$msg = '';
|
$msg = '';
|
||||||
if (isset($_POST['formSent']) && intval($_POST['formSent']) == 1) {
|
if (isset($_POST['formSent']) && intval($_POST['formSent']) == 1 && Security::check_token('post')) {
|
||||||
$courses_list = isset($_POST['CoursesList']) ? $_POST['CoursesList'] : [];
|
$courses_list = isset($_POST['CoursesList']) ? $_POST['CoursesList'] : [];
|
||||||
$affected_rows = CourseManager::subscribeCoursesToDrhManager($user_id, $courses_list);
|
$affected_rows = CourseManager::subscribeCoursesToDrhManager($user_id, $courses_list);
|
||||||
if ($affected_rows) {
|
if ($affected_rows) {
|
||||||
@@ -219,6 +219,7 @@ $result = Database::query($sql);
|
|||||||
?>
|
?>
|
||||||
<form name="formulaire" method="post" action="<?php echo api_get_self(); ?>?user=<?php echo $user_id; ?>" style="margin:0px;">
|
<form name="formulaire" method="post" action="<?php echo api_get_self(); ?>?user=<?php echo $user_id; ?>" style="margin:0px;">
|
||||||
<input type="hidden" name="formSent" value="1" />
|
<input type="hidden" name="formSent" value="1" />
|
||||||
|
<?php echo Security::get_HTML_token(); ?>
|
||||||
<?php
|
<?php
|
||||||
if (!empty($msg)) {
|
if (!empty($msg)) {
|
||||||
echo Display::return_message($msg, 'normal'); //main API
|
echo Display::return_message($msg, 'normal'); //main API
|
||||||
|
|||||||
@@ -271,6 +271,8 @@ $language_data = [];
|
|||||||
while ($row = Database::fetch_array($result_select)) {
|
while ($row = Database::fetch_array($result_select)) {
|
||||||
$row_td = [];
|
$row_td = [];
|
||||||
$row_td[] = $row['id'];
|
$row_td[] = $row['id'];
|
||||||
|
// escape the original name for safe output in HTML attribute and body contexts
|
||||||
|
$originalName = htmlspecialchars($row['original_name'], ENT_QUOTES, 'UTF-8');
|
||||||
// the first column is the original name of the language OR a form containing the original name
|
// the first column is the original name of the language OR a form containing the original name
|
||||||
if ($action == 'edit' and $row['id'] == $_GET['id']) {
|
if ($action == 'edit' and $row['id'] == $_GET['id']) {
|
||||||
$checked = '';
|
$checked = '';
|
||||||
@@ -278,10 +280,10 @@ while ($row = Database::fetch_array($result_select)) {
|
|||||||
$checked = ' checked="checked" ';
|
$checked = ' checked="checked" ';
|
||||||
}
|
}
|
||||||
|
|
||||||
$row_td[] = '<input type="hidden" name="edit_id" value="'.Security::remove_XSS($_GET['id']).'" /><input type="text" name="txt_name" value="'.$row['original_name'].'" /> '
|
$row_td[] = '<input type="hidden" name="edit_id" value="'.Security::remove_XSS($_GET['id']).'" /><input type="text" name="txt_name" value="'.$originalName.'" /> '
|
||||||
.'<input type="checkbox" '.$checked.'name="platformlanguage" id="platformlanguage" value="'.$row['english_name'].'" /><label for="platformlanguage">'.$row['original_name'].' '.get_lang('AsPlatformLanguage').'</label> <input type="submit" name="Submit" value="'.get_lang('Ok').'" /><a name="value" />';
|
.'<input type="checkbox" '.$checked.'name="platformlanguage" id="platformlanguage" value="'.htmlspecialchars($row['english_name'], ENT_QUOTES, 'UTF-8').'" /><label for="platformlanguage">'.$originalName.' '.get_lang('AsPlatformLanguage').'</label> <input type="submit" name="Submit" value="'.get_lang('Ok').'" /><a name="value" />';
|
||||||
} else {
|
} else {
|
||||||
$row_td[] = $row['original_name'];
|
$row_td[] = $originalName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the second column
|
// the second column
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ if (empty($annee) && empty($course)) {
|
|||||||
|
|
||||||
//$sr = @ ldap_search($ds, "ou=people,$LDAPbasedn", "(|(edupersonprimaryorgunitdn=ou=$etape,ou=$annee,ou=diploma,o=Paris1,$LDAPbasedn)(edupersonprimaryorgunitdn=ou=02PEL,ou=$annee,ou=diploma,o=Paris1,$LDAPbasedn))");
|
//$sr = @ ldap_search($ds, "ou=people,$LDAPbasedn", "(|(edupersonprimaryorgunitdn=ou=$etape,ou=$annee,ou=diploma,o=Paris1,$LDAPbasedn)(edupersonprimaryorgunitdn=ou=02PEL,ou=$annee,ou=diploma,o=Paris1,$LDAPbasedn))");
|
||||||
//echo "(ou=*$annee,ou=$composante)";
|
//echo "(ou=*$annee,ou=$composante)";
|
||||||
$sr = @ldap_search($ds, $ldap_basedn, "(ou=*$annee)");
|
$anneeFilter = ldap_escape($annee, "", LDAP_ESCAPE_FILTER);
|
||||||
|
$sr = @ldap_search($ds, $ldap_basedn, "(ou=*$anneeFilter)");
|
||||||
|
|
||||||
$info = ldap_get_entries($ds, $sr);
|
$info = ldap_get_entries($ds, $sr);
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ elseif (!empty($annee) && !empty($id_session) && empty($_POST['confirmed'])) {
|
|||||||
|
|
||||||
//$sr = @ ldap_search($ds, "ou=people,$LDAPbasedn", "(|(edupersonprimaryorgunitdn=ou=$etape,ou=$annee,ou=diploma,o=Paris1,$LDAPbasedn)(edupersonprimaryorgunitdn=ou=02PEL,ou=$annee,ou=diploma,o=Paris1,$LDAPbasedn))");
|
//$sr = @ ldap_search($ds, "ou=people,$LDAPbasedn", "(|(edupersonprimaryorgunitdn=ou=$etape,ou=$annee,ou=diploma,o=Paris1,$LDAPbasedn)(edupersonprimaryorgunitdn=ou=02PEL,ou=$annee,ou=diploma,o=Paris1,$LDAPbasedn))");
|
||||||
//echo "(ou=*$annee,ou=$composante)";
|
//echo "(ou=*$annee,ou=$composante)";
|
||||||
$sr = @ldap_search($ds, $ldap_basedn, "(ou=*$annee)");
|
$anneeFilter = ldap_escape($annee, "", LDAP_ESCAPE_FILTER);
|
||||||
|
$sr = @ldap_search($ds, $ldap_basedn, "(ou=*$anneeFilter)");
|
||||||
|
|
||||||
$info = ldap_get_entries($ds, $sr);
|
$info = ldap_get_entries($ds, $sr);
|
||||||
|
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ if ($languageId) {
|
|||||||
$form->addHidden('sub', $languageInfo['id']);
|
$form->addHidden('sub', $languageInfo['id']);
|
||||||
$form->addHidden('sub_language_id', $languageInfo['id']);
|
$form->addHidden('sub_language_id', $languageInfo['id']);
|
||||||
$form->addHidden('redirect', true);
|
$form->addHidden('redirect', true);
|
||||||
|
$form->addHidden('sec_token', Security::get_token());
|
||||||
$form->addButtonSave(get_lang('Save'));
|
$form->addButtonSave(get_lang('Save'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ $htmlHeadXtra[] = '<script>
|
|||||||
\'file_id\': file_id,
|
\'file_id\': file_id,
|
||||||
\'id\': '.intval($_REQUEST['id']).',
|
\'id\': '.intval($_REQUEST['id']).',
|
||||||
\'sub\': '.intval($_REQUEST['sub_language_id']).',
|
\'sub\': '.intval($_REQUEST['sub_language_id']).',
|
||||||
\'sub_language_id\': '.intval($_REQUEST['sub_language_id']).'
|
\'sub_language_id\': '.intval($_REQUEST['sub_language_id']).',
|
||||||
|
\'sec_token\': \''.Security::get_token().'\'
|
||||||
},
|
},
|
||||||
success: function(datos) {
|
success: function(datos) {
|
||||||
if (datos == "1") {
|
if (datos == "1") {
|
||||||
|
|||||||
@@ -244,6 +244,18 @@ if (isset($_POST['SubmitAddNewLanguage'])) {
|
|||||||
//Fixes BT#1636
|
//Fixes BT#1636
|
||||||
$english_name = api_strtolower($english_name);
|
$english_name = api_strtolower($english_name);
|
||||||
|
|
||||||
|
// Validate the folder name is safe for filesystem use
|
||||||
|
if (!SubLanguageManager::isValidLanguageFolderName($english_name)) {
|
||||||
|
$msg .= Display::return_message(
|
||||||
|
get_lang('EnglishName').' '.get_lang('ContainsIllegalCharacters'),
|
||||||
|
'error'
|
||||||
|
);
|
||||||
|
Display::display_header($language_name);
|
||||||
|
echo $msg;
|
||||||
|
Display::display_footer();
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
$isocode = str_replace(' ', '_', $isocode);
|
$isocode = str_replace(' ', '_', $isocode);
|
||||||
$str_info = '<br/>'.get_lang('OriginalName').' : '.$original_name.'<br/>'.get_lang('EnglishName').' : '.$english_name.'<br/>'.get_lang('PlatformCharsetTitle').' : '.$isocode;
|
$str_info = '<br/>'.get_lang('OriginalName').' : '.$original_name.'<br/>'.get_lang('EnglishName').' : '.$english_name.'<br/>'.get_lang('PlatformCharsetTitle').' : '.$isocode;
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ require_once __DIR__.'/../inc/global.inc.php';
|
|||||||
|
|
||||||
api_protect_admin_script();
|
api_protect_admin_script();
|
||||||
|
|
||||||
|
// CSRF protection
|
||||||
|
if (!isset($_REQUEST['sec_token']) || !Security::check_token('request')) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$new_language = Security::remove_XSS($_REQUEST['new_language']);
|
$new_language = Security::remove_XSS($_REQUEST['new_language']);
|
||||||
$language_variable = ltrim(
|
$language_variable = ltrim(
|
||||||
Security::remove_XSS($_REQUEST['variable_language']),
|
Security::remove_XSS($_REQUEST['variable_language']),
|
||||||
@@ -20,7 +25,7 @@ $language_variable = ltrim(
|
|||||||
);
|
);
|
||||||
$file_id = intval($_REQUEST['file_id']);
|
$file_id = intval($_REQUEST['file_id']);
|
||||||
|
|
||||||
$variableIsValid = isset($language_variable) && preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $language_variable);
|
$variableIsValid = SubLanguageManager::isValidLanguageVariable($language_variable);
|
||||||
|
|
||||||
if (isset($new_language) && $variableIsValid && isset($file_id)) {
|
if (isset($new_language) && $variableIsValid && isset($file_id)) {
|
||||||
$file_language = $language_files_to_load[$file_id].'.inc.php';
|
$file_language = $language_files_to_load[$file_id].'.inc.php';
|
||||||
@@ -28,6 +33,12 @@ if (isset($new_language) && $variableIsValid && isset($file_id)) {
|
|||||||
$sub_language_id = intval($_REQUEST['sub']);
|
$sub_language_id = intval($_REQUEST['sub']);
|
||||||
$all_data_of_language = SubLanguageManager::get_all_information_of_sub_language($id_language, $sub_language_id);
|
$all_data_of_language = SubLanguageManager::get_all_information_of_sub_language($id_language, $sub_language_id);
|
||||||
|
|
||||||
|
if (empty($all_data_of_language) ||
|
||||||
|
!SubLanguageManager::isValidLanguageFolderName($all_data_of_language['dokeos_folder'])
|
||||||
|
) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$path_folder = api_get_path(SYS_LANG_PATH).$all_data_of_language['dokeos_folder'].'/'.$file_language;
|
$path_folder = api_get_path(SYS_LANG_PATH).$all_data_of_language['dokeos_folder'].'/'.$file_language;
|
||||||
$all_file_of_directory = SubLanguageManager::get_all_language_variable_in_file($path_folder);
|
$all_file_of_directory = SubLanguageManager::get_all_language_variable_in_file($path_folder);
|
||||||
$return_value = SubLanguageManager::add_file_in_language_directory($path_folder);
|
$return_value = SubLanguageManager::add_file_in_language_directory($path_folder);
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ function save_data(array $users, bool $sendMail = false, ?string $targetFolder =
|
|||||||
}
|
}
|
||||||
|
|
||||||
$email = $user['Email'] ?? $userInfo['email'];
|
$email = $user['Email'] ?? $userInfo['email'];
|
||||||
$status = $user['Status'] ?? $userInfo['status'];
|
$status = api_status_key($user['Status']) ?? $userInfo['status'];
|
||||||
$officialCode = $user['OfficialCode'] ?? $userInfo['official_code'];
|
$officialCode = $user['OfficialCode'] ?? $userInfo['official_code'];
|
||||||
$phone = $user['PhoneNumber'] ?? $userInfo['phone'];
|
$phone = $user['PhoneNumber'] ?? $userInfo['phone'];
|
||||||
$pictureUrl = $user['PictureUri'] ?? $userInfo['picture_uri'];
|
$pictureUrl = $user['PictureUri'] ?? $userInfo['picture_uri'];
|
||||||
|
|||||||
@@ -469,10 +469,15 @@ function get_user_data($from, $number_of_items, $column, $direction)
|
|||||||
$user[0],
|
$user[0],
|
||||||
USER_IMAGE_SIZE_SMALL
|
USER_IMAGE_SIZE_SMALL
|
||||||
);
|
);
|
||||||
|
$personName = htmlspecialchars(
|
||||||
|
api_get_person_name($user[2], $user[3]),
|
||||||
|
ENT_QUOTES,
|
||||||
|
'UTF-8'
|
||||||
|
);
|
||||||
$photo = '<img
|
$photo = '<img
|
||||||
src="'.$userPicture.'" width="22" height="22"
|
src="'.$userPicture.'" width="22" height="22"
|
||||||
alt="'.api_get_person_name($user[2], $user[3]).'"
|
alt="'.$personName.'"
|
||||||
title="'.api_get_person_name($user[2], $user[3]).'" />';
|
title="'.$personName.'" />';
|
||||||
|
|
||||||
if (1 == $user[7] && !empty($user['exp'])) {
|
if (1 == $user[7] && !empty($user['exp'])) {
|
||||||
// check expiration date
|
// check expiration date
|
||||||
|
|||||||
@@ -235,9 +235,9 @@ if (!empty($user_list)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($course_list_registered as &$course) {
|
foreach ($course_list_registered as $i => $course) {
|
||||||
$courseInfo = api_get_course_info_by_id($course['real_id']);
|
$courseInfo = api_get_course_info_by_id($course['real_id']);
|
||||||
$course['name'] = $courseInfo['name'];
|
$course_list_registered[$i]['name'] = $courseInfo['name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$course_list = $course_list_registered;
|
$course_list = $course_list_registered;
|
||||||
|
|||||||
@@ -277,8 +277,8 @@ function extldap_get_user_search_string($username)
|
|||||||
global $extldap_config;
|
global $extldap_config;
|
||||||
// init
|
// init
|
||||||
$filter = '('.$extldap_config['user_search'].')';
|
$filter = '('.$extldap_config['user_search'].')';
|
||||||
// replacing %username% by the actual username
|
// replacing %username% by the actual username (escaped to prevent LDAP injection)
|
||||||
$filter = str_replace('%username%', $username, $filter);
|
$filter = str_replace('%username%', ldap_escape($username, "", LDAP_ESCAPE_FILTER), $filter);
|
||||||
// append a global filter if needed
|
// append a global filter if needed
|
||||||
if (isset($extldap_config['filter']) && $extldap_config['filter'] != "") {
|
if (isset($extldap_config['filter']) && $extldap_config['filter'] != "") {
|
||||||
$filter = '(&'.$filter.'('.$extldap_config['filter'].'))';
|
$filter = '(&'.$filter.'('.$extldap_config['filter'].'))';
|
||||||
@@ -518,5 +518,7 @@ function extldapCasUserLogin($casUser)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// return the value
|
// return the value
|
||||||
return extldapGetUserAttributeValue("($attributeToFilterOn=$casUser)", $attributeToRead);
|
$casUserFilter = ldap_escape($casUser, "", LDAP_ESCAPE_FILTER);
|
||||||
|
|
||||||
|
return extldapGetUserAttributeValue("($attributeToFilterOn=$casUserFilter)", $attributeToRead);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -680,7 +680,20 @@ if ($form->validate()) {
|
|||||||
$values['username'] = api_substr($values['username'], 0, USERNAME_MAX_LENGTH);
|
$values['username'] = api_substr($values['username'], 0, USERNAME_MAX_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (api_get_setting('allow_registration_as_teacher') === 'false') {
|
// Security rule: if teacher registration is disabled, force learner status.
|
||||||
|
$allowTeacherRegistration = api_get_setting('allow_registration_as_teacher') !== 'false';
|
||||||
|
if (!$allowTeacherRegistration) {
|
||||||
|
$values['status'] = STUDENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Security rule: server-side allow-list on submitted status to prevent
|
||||||
|
// privilege mass-assignment (CWE-915). The UI only offers STUDENT/COURSEMANAGER;
|
||||||
|
// any other value (e.g. SESSIONADMIN, DRH, COURSEMANAGERLOWSECURITY) coming
|
||||||
|
// from a tampered POST must be downgraded to STUDENT.
|
||||||
|
$allowedSelfRegistrationStatus = $allowTeacherRegistration
|
||||||
|
? [STUDENT, COURSEMANAGER]
|
||||||
|
: [STUDENT];
|
||||||
|
if (!in_array((int) ($values['status'] ?? STUDENT), $allowedSelfRegistrationStatus, true)) {
|
||||||
$values['status'] = STUDENT;
|
$values['status'] = STUDENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -117,10 +117,11 @@ function ldap_find_user_info($login)
|
|||||||
// Search surname entry
|
// Search surname entry
|
||||||
//OLD: $sr=ldap_search($ldapconnect,"dc=rug, dc=ac, dc=be", "uid=$login");
|
//OLD: $sr=ldap_search($ldapconnect,"dc=rug, dc=ac, dc=be", "uid=$login");
|
||||||
//echo "<p> ldapDc = '$LDAPbasedn' </p>";
|
//echo "<p> ldapDc = '$LDAPbasedn' </p>";
|
||||||
|
$loginFilter = ldap_escape($login, "", LDAP_ESCAPE_FILTER);
|
||||||
if (!empty($ldap_search_dn)) {
|
if (!empty($ldap_search_dn)) {
|
||||||
$sr = ldap_search($ldap_connect, $ldap_search_dn, "uid=$login");
|
$sr = ldap_search($ldap_connect, $ldap_search_dn, "uid=$loginFilter");
|
||||||
} else {
|
} else {
|
||||||
$sr = ldap_search($ldap_connect, $ldap_basedn, "uid=$login");
|
$sr = ldap_search($ldap_connect, $ldap_basedn, "uid=$loginFilter");
|
||||||
}
|
}
|
||||||
//echo " Search result is ".$sr;
|
//echo " Search result is ".$sr;
|
||||||
//echo " Number of entries returned is ".ldap_count_entries($ldapconnect,$sr);
|
//echo " Number of entries returned is ".ldap_count_entries($ldapconnect,$sr);
|
||||||
@@ -272,7 +273,8 @@ function ldap_authentication_check($uname, $passwd)
|
|||||||
if ($ds !== false) {
|
if ($ds !== false) {
|
||||||
//Creation of filter containing values input by the user
|
//Creation of filter containing values input by the user
|
||||||
// Here it might be necessary to use $filter="(samaccountName=$uname)"; - see http://support.chamilo.org/issues/4675
|
// Here it might be necessary to use $filter="(samaccountName=$uname)"; - see http://support.chamilo.org/issues/4675
|
||||||
$filter = "(uid=$uname)";
|
$unameFilter = ldap_escape($uname, "", LDAP_ESCAPE_FILTER);
|
||||||
|
$filter = "(uid=$unameFilter)";
|
||||||
// Open anonymous LDAP connection
|
// Open anonymous LDAP connection
|
||||||
$result = false;
|
$result = false;
|
||||||
$ldap_bind_res = ldap_handle_bind($ds, $result);
|
$ldap_bind_res = ldap_handle_bind($ds, $result);
|
||||||
@@ -371,10 +373,13 @@ function ldap_get_users()
|
|||||||
{
|
{
|
||||||
global $ldap_basedn, $ldap_host, $ldap_port, $ldap_rdn, $ldap_pass, $ldap_search_dn, $extldap_user_correspondance;
|
global $ldap_basedn, $ldap_host, $ldap_port, $ldap_rdn, $ldap_pass, $ldap_search_dn, $extldap_user_correspondance;
|
||||||
|
|
||||||
$keyword_firstname = isset($_GET['keyword_firstname']) ? trim(Database::escape_string($_GET['keyword_firstname'])) : '';
|
// These values are interpolated into LDAP filters, so they must be escaped
|
||||||
$keyword_lastname = isset($_GET['keyword_lastname']) ? trim(Database::escape_string($_GET['keyword_lastname'])) : '';
|
// with ldap_escape() (LDAP_ESCAPE_FILTER); SQL escaping does not neutralize
|
||||||
$keyword_username = isset($_GET['keyword_username']) ? trim(Database::escape_string($_GET['keyword_username'])) : '';
|
// LDAP metacharacters and would corrupt the search.
|
||||||
$keyword_type = isset($_GET['keyword_type']) ? Database::escape_string($_GET['keyword_type']) : '';
|
$keyword_firstname = isset($_GET['keyword_firstname']) ? ldap_escape(trim($_GET['keyword_firstname']), "", LDAP_ESCAPE_FILTER) : '';
|
||||||
|
$keyword_lastname = isset($_GET['keyword_lastname']) ? ldap_escape(trim($_GET['keyword_lastname']), "", LDAP_ESCAPE_FILTER) : '';
|
||||||
|
$keyword_username = isset($_GET['keyword_username']) ? ldap_escape(trim($_GET['keyword_username']), "", LDAP_ESCAPE_FILTER) : '';
|
||||||
|
$keyword_type = isset($_GET['keyword_type']) ? ldap_escape($_GET['keyword_type'], "", LDAP_ESCAPE_FILTER) : '';
|
||||||
|
|
||||||
$ldap_query = [];
|
$ldap_query = [];
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ require_once __DIR__.'/../inc/global.inc.php';
|
|||||||
$this_section = SECTION_MYAGENDA;
|
$this_section = SECTION_MYAGENDA;
|
||||||
api_block_anonymous_users();
|
api_block_anonymous_users();
|
||||||
|
|
||||||
|
// Only redirect back to the referrer when it points to this Chamilo portal;
|
||||||
|
// otherwise fall back to the portal root to prevent an open redirect.
|
||||||
|
$webPath = api_get_path(WEB_PATH);
|
||||||
|
$referer = $_SERVER['HTTP_REFERER'] ?? '';
|
||||||
|
$redirectUrl = (is_string($referer) && 0 === strpos($referer, $webPath)) ? $referer : $webPath;
|
||||||
|
|
||||||
// setting the name of the tool
|
// setting the name of the tool
|
||||||
$nameTools = get_lang('MyAgenda');
|
$nameTools = get_lang('MyAgenda');
|
||||||
|
|
||||||
@@ -77,7 +83,7 @@ if (!empty($event)) {
|
|||||||
case 'platform':
|
case 'platform':
|
||||||
$vevent->setProperty('summary', api_convert_encoding($event['title'], 'UTF-8', $charset));
|
$vevent->setProperty('summary', api_convert_encoding($event['title'], 'UTF-8', $charset));
|
||||||
if (empty($event['start_date'])) {
|
if (empty($event['start_date'])) {
|
||||||
header('location:'.Security::remove_XSS($_SERVER['HTTP_REFERER']));
|
header('location:'.$redirectUrl);
|
||||||
}
|
}
|
||||||
list($y, $m, $d, $h, $M, $s) = preg_split('/[\s:-]/', $event['start_date']);
|
list($y, $m, $d, $h, $M, $s) = preg_split('/[\s:-]/', $event['start_date']);
|
||||||
$vevent->setProperty(
|
$vevent->setProperty(
|
||||||
@@ -116,7 +122,7 @@ if (!empty($event)) {
|
|||||||
case 'course':
|
case 'course':
|
||||||
$vevent->setProperty('summary', api_convert_encoding($event['title'], 'UTF-8', $charset));
|
$vevent->setProperty('summary', api_convert_encoding($event['title'], 'UTF-8', $charset));
|
||||||
if (empty($event['start_date'])) {
|
if (empty($event['start_date'])) {
|
||||||
header('location:'.Security::remove_XSS($_SERVER['HTTP_REFERER']));
|
header('location:'.$redirectUrl);
|
||||||
}
|
}
|
||||||
list($y, $m, $d, $h, $M, $s) = preg_split('/[\s:-]/', $event['start_date']);
|
list($y, $m, $d, $h, $M, $s) = preg_split('/[\s:-]/', $event['start_date']);
|
||||||
$vevent->setProperty(
|
$vevent->setProperty(
|
||||||
@@ -160,10 +166,10 @@ if (!empty($event)) {
|
|||||||
$ical->returnCalendar();
|
$ical->returnCalendar();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
header('location:'.Security::remove_XSS($_SERVER['HTTP_REFERER']));
|
header('location:'.$redirectUrl);
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
header('location:'.Security::remove_XSS($_SERVER['HTTP_REFERER']));
|
header('location:'.$redirectUrl);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class CourseDescriptionController
|
|||||||
if (!empty($_POST['title']) && !empty($_POST['contentDescription'])) {
|
if (!empty($_POST['title']) && !empty($_POST['contentDescription'])) {
|
||||||
if (1) {
|
if (1) {
|
||||||
$title = $_POST['title'];
|
$title = $_POST['title'];
|
||||||
$content = $_POST['contentDescription'];
|
$content = Security::remove_XSS($_POST['contentDescription'], COURSEMANAGERLOWSECURITY);
|
||||||
$description_type = $_POST['description_type'];
|
$description_type = $_POST['description_type'];
|
||||||
$id = $_POST['id'];
|
$id = $_POST['id'];
|
||||||
if (empty($id)) {
|
if (empty($id)) {
|
||||||
@@ -253,7 +253,7 @@ class CourseDescriptionController
|
|||||||
if (!empty($_POST['title']) && !empty($_POST['contentDescription'])) {
|
if (!empty($_POST['title']) && !empty($_POST['contentDescription'])) {
|
||||||
if (1) {
|
if (1) {
|
||||||
$title = $_POST['title'];
|
$title = $_POST['title'];
|
||||||
$content = $_POST['contentDescription'];
|
$content = Security::remove_XSS($_POST['contentDescription'], COURSEMANAGERLOWSECURITY);
|
||||||
$description_type = $_POST['description_type'];
|
$description_type = $_POST['description_type'];
|
||||||
if ($description_type >= ADD_BLOCK) {
|
if ($description_type >= ADD_BLOCK) {
|
||||||
$course_description->set_description_type($description_type);
|
$course_description->set_description_type($description_type);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
/* For licensing terms, see /license.txt */
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Essence\SafeEssenceHttpClient;
|
||||||
use Chamilo\CoreBundle\Entity\CourseRelUser;
|
use Chamilo\CoreBundle\Entity\CourseRelUser;
|
||||||
use Chamilo\CoreBundle\Entity\ExtraField;
|
use Chamilo\CoreBundle\Entity\ExtraField;
|
||||||
use Chamilo\CoreBundle\Entity\Repository\SequenceResourceRepository;
|
use Chamilo\CoreBundle\Entity\Repository\SequenceResourceRepository;
|
||||||
@@ -195,7 +196,11 @@ if ($hasRequirements) {
|
|||||||
$template = new Template($course->getTitle(), true, true, false, true, false);
|
$template = new Template($course->getTitle(), true, true, false, true, false);
|
||||||
|
|
||||||
$template->assign('course', $courseItem);
|
$template->assign('course', $courseItem);
|
||||||
$essence = Essence\Essence::instance();
|
// SSRF protection: route Essence's server-side OEmbed/OpenGraph fetches of the
|
||||||
|
// teacher-set video URL through an IP-filtered HTTP client.
|
||||||
|
$essence = Essence\Essence::instance(
|
||||||
|
['Http' => new SafeEssenceHttpClient()]
|
||||||
|
);
|
||||||
$template->assign('essence', $essence);
|
$template->assign('essence', $essence);
|
||||||
$template->assign('is_premium', $courseIsPremium);
|
$template->assign('is_premium', $courseIsPremium);
|
||||||
$template->assign('allow_subscribe', $allowSubscribe);
|
$template->assign('allow_subscribe', $allowSubscribe);
|
||||||
|
|||||||
@@ -41,17 +41,51 @@ Display::display_header($nameTools);
|
|||||||
echo Display::page_header($nameTools);
|
echo Display::page_header($nameTools);
|
||||||
$action = isset($_POST['action']) ? $_POST['action'] : '';
|
$action = isset($_POST['action']) ? $_POST['action'] : '';
|
||||||
$exportOption = isset($_POST['export_option']) ? $_POST['export_option'] : '';
|
$exportOption = isset($_POST['export_option']) ? $_POST['export_option'] : '';
|
||||||
|
$debugMoodleExport = MoodleExport::isDebugEnabled();
|
||||||
|
MoodleExport::registerDebugShutdownHandler();
|
||||||
|
|
||||||
|
if ($debugMoodleExport) {
|
||||||
|
MoodleExport::debugStaticLog('Debug mode enabled from MoodleExport::$debugEnabled', [
|
||||||
|
'script' => 'export_moodle.php',
|
||||||
|
'course_id' => api_get_course_id(),
|
||||||
|
'course_code' => api_get_course_id(),
|
||||||
|
'cidreq' => api_get_cidreq(),
|
||||||
|
'request_method' => $_SERVER['REQUEST_METHOD'] ?? '',
|
||||||
|
'post_action' => $action,
|
||||||
|
'export_option' => $exportOption,
|
||||||
|
'post_keys' => array_keys($_POST),
|
||||||
|
'content_length' => $_SERVER['CONTENT_LENGTH'] ?? '',
|
||||||
|
'debug_file' => MoodleExport::getDebugFilePath(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
// Handle course selection form submission
|
// Handle course selection form submission
|
||||||
|
MoodleExport::debugStaticLog('Checking export form branch', [
|
||||||
|
'action' => $action,
|
||||||
|
'has_post_token' => isset($_POST['sec_token']),
|
||||||
|
]);
|
||||||
|
|
||||||
if ($action === 'course_select_form' && Security::check_token('post')) {
|
if ($action === 'course_select_form' && Security::check_token('post')) {
|
||||||
|
MoodleExport::debugStaticLog('Course selection form submitted');
|
||||||
|
|
||||||
// Handle the selected resources and continue with export
|
// Handle the selected resources and continue with export
|
||||||
$selectedResources = $_POST['resource'] ?? null;
|
$selectedResources = $_POST['resource'] ?? null;
|
||||||
|
MoodleExport::debugStaticLog('Selected resources received', [
|
||||||
|
'resource_groups' => is_array($selectedResources) ? count($selectedResources) : 0,
|
||||||
|
]);
|
||||||
|
|
||||||
if (!empty($selectedResources)) {
|
if (!empty($selectedResources)) {
|
||||||
// Rebuild the course object based on selected resources
|
// Rebuild the course object based on selected resources
|
||||||
|
MoodleExport::debugStaticLog('Building partial course from selected resources');
|
||||||
$cb = new CourseBuilder('partial');
|
$cb = new CourseBuilder('partial');
|
||||||
$course = $cb->build(0, null, false, array_keys($selectedResources), $selectedResources);
|
$course = $cb->build(0, null, false, array_keys($selectedResources), $selectedResources);
|
||||||
|
MoodleExport::restoreMainDatabaseConnection();
|
||||||
|
MoodleExport::debugStaticLog('Partial course from selected resources built');
|
||||||
|
|
||||||
|
MoodleExport::debugStaticLog('Normalizing posted course selection');
|
||||||
$course = CourseSelectForm::get_posted_course(null, 0, '', $course);
|
$course = CourseSelectForm::get_posted_course(null, 0, '', $course);
|
||||||
|
MoodleExport::restoreMainDatabaseConnection();
|
||||||
|
MoodleExport::debugStaticLog('Posted course selection normalized');
|
||||||
|
|
||||||
// Get admin details
|
// Get admin details
|
||||||
$adminId = (int) $_POST['admin_id'];
|
$adminId = (int) $_POST['admin_id'];
|
||||||
@@ -67,15 +101,32 @@ if ($action === 'course_select_form' && Security::check_token('post')) {
|
|||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MoodleExport::debugStaticLog('Creating MoodleExport instance for selected resources');
|
||||||
$exporter = new MoodleExport($course);
|
$exporter = new MoodleExport($course);
|
||||||
|
MoodleExport::debugStaticLog('MoodleExport instance created for selected resources');
|
||||||
|
|
||||||
$exporter->setAdminUserData($adminId, $adminUsername, $adminEmail);
|
$exporter->setAdminUserData($adminId, $adminUsername, $adminEmail);
|
||||||
|
MoodleExport::debugStaticLog('Admin user data configured', [
|
||||||
|
'admin_id' => $adminId,
|
||||||
|
'admin_username' => $adminUsername,
|
||||||
|
'admin_email' => $adminEmail,
|
||||||
|
]);
|
||||||
|
|
||||||
// Perform export
|
// Perform export
|
||||||
$courseId = api_get_course_id();
|
$courseId = api_get_course_id();
|
||||||
$exportDir = 'moodle_export_'.$courseId;
|
$exportDir = 'moodle_export_'.$courseId;
|
||||||
try {
|
try {
|
||||||
$moodleVersion = isset($_POST['moodle_version']) ? (int) $_POST['moodle_version'] : 3;
|
$moodleVersion = isset($_POST['moodle_version']) ? (int) $_POST['moodle_version'] : 3;
|
||||||
|
MoodleExport::debugStaticLog('Starting selected resources Moodle export', [
|
||||||
|
'course_id' => $courseId,
|
||||||
|
'export_dir' => $exportDir,
|
||||||
|
'moodle_version' => $moodleVersion,
|
||||||
|
]);
|
||||||
|
|
||||||
$mbzFile = $exporter->export($courseId, $exportDir, $moodleVersion);
|
$mbzFile = $exporter->export($courseId, $exportDir, $moodleVersion);
|
||||||
|
MoodleExport::debugStaticLog('Selected resources Moodle export finished', [
|
||||||
|
'mbz_file' => $mbzFile,
|
||||||
|
]);
|
||||||
|
|
||||||
echo Display::return_message(get_lang('MoodleExportCreated'), 'confirm');
|
echo Display::return_message(get_lang('MoodleExportCreated'), 'confirm');
|
||||||
echo '<br />';
|
echo '<br />';
|
||||||
@@ -84,7 +135,14 @@ if ($action === 'course_select_form' && Security::check_token('post')) {
|
|||||||
api_get_path(WEB_CODE_PATH).'course_info/download.php?archive_path=1&archive='.basename($mbzFile).'&'.api_get_cidreq(),
|
api_get_path(WEB_CODE_PATH).'course_info/download.php?archive_path=1&archive='.basename($mbzFile).'&'.api_get_cidreq(),
|
||||||
['class' => 'btn btn-primary btn-large']
|
['class' => 'btn btn-primary btn-large']
|
||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
} catch (Throwable $e) {
|
||||||
|
MoodleExport::restoreMainDatabaseConnection();
|
||||||
|
|
||||||
|
if ($debugMoodleExport) {
|
||||||
|
error_log('[MoodleExport] Export failed: '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine());
|
||||||
|
error_log('[MoodleExport] Stack trace: '.$e->getTraceAsString());
|
||||||
|
}
|
||||||
|
|
||||||
echo Display::return_message(get_lang('ErrorCreatingExport').': '.$e->getMessage(), 'error');
|
echo Display::return_message(get_lang('ErrorCreatingExport').': '.$e->getMessage(), 'error');
|
||||||
}
|
}
|
||||||
exit();
|
exit();
|
||||||
@@ -92,11 +150,14 @@ if ($action === 'course_select_form' && Security::check_token('post')) {
|
|||||||
echo Display::return_message(get_lang('NoResourcesSelected'), 'warning');
|
echo Display::return_message(get_lang('NoResourcesSelected'), 'warning');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
MoodleExport::debugStaticLog('Displaying initial export form or processing initial form submission');
|
||||||
|
|
||||||
$form = new FormValidator(
|
$form = new FormValidator(
|
||||||
'create_export_form',
|
'create_export_form',
|
||||||
'post',
|
'post',
|
||||||
api_get_self().'?'.api_get_cidreq()
|
api_get_self().'?'.api_get_cidreq()
|
||||||
);
|
);
|
||||||
|
|
||||||
$form->addElement('radio', 'export_option', '', get_lang('CreateFullBackup'), 'full_export');
|
$form->addElement('radio', 'export_option', '', get_lang('CreateFullBackup'), 'full_export');
|
||||||
$form->addElement('radio', 'export_option', '', get_lang('LetMeSelectItems'), 'select_items');
|
$form->addElement('radio', 'export_option', '', get_lang('LetMeSelectItems'), 'select_items');
|
||||||
$form->addElement('select', 'moodle_version', get_lang('MoodleVersion'), [
|
$form->addElement('select', 'moodle_version', get_lang('MoodleVersion'), [
|
||||||
@@ -119,27 +180,59 @@ if ($action === 'course_select_form' && Security::check_token('post')) {
|
|||||||
|
|
||||||
// Add buttons
|
// Add buttons
|
||||||
$form->addButtonSave(get_lang('CreateExport'));
|
$form->addButtonSave(get_lang('CreateExport'));
|
||||||
$form->addProgress();
|
MoodleExport::debugStaticLog('Initial export form built');
|
||||||
|
MoodleExport::debugStaticLog('Initial export form validate call started');
|
||||||
|
$isInitialExportFormValid = $form->validate();
|
||||||
|
MoodleExport::debugStaticLog('Initial export form validate call finished', [
|
||||||
|
'is_valid' => $isInitialExportFormValid,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($isInitialExportFormValid) {
|
||||||
|
MoodleExport::debugStaticLog('Initial export form validated');
|
||||||
|
|
||||||
if ($form->validate()) {
|
|
||||||
$values = $form->exportValues();
|
$values = $form->exportValues();
|
||||||
|
MoodleExport::debugStaticLog('Initial export form values exported', [
|
||||||
|
'export_option' => (string) ($values['export_option'] ?? ''),
|
||||||
|
'moodle_version' => (string) ($values['moodle_version'] ?? ''),
|
||||||
|
]);
|
||||||
$adminId = (int) $values['admin_id'];
|
$adminId = (int) $values['admin_id'];
|
||||||
$adminUsername = $values['admin_username'];
|
$adminUsername = $values['admin_username'];
|
||||||
$adminEmail = $values['admin_email'];
|
$adminEmail = $values['admin_email'];
|
||||||
|
|
||||||
if ($values['export_option'] === 'full_export') {
|
if ($values['export_option'] === 'full_export') {
|
||||||
|
MoodleExport::debugStaticLog('Full export selected, building complete course');
|
||||||
$cb = new CourseBuilder('complete');
|
$cb = new CourseBuilder('complete');
|
||||||
$course = $cb->build();
|
$course = $cb->build();
|
||||||
|
MoodleExport::restoreMainDatabaseConnection();
|
||||||
|
MoodleExport::debugStaticLog('Complete course built for full export');
|
||||||
|
|
||||||
|
MoodleExport::debugStaticLog('Creating MoodleExport instance for full export');
|
||||||
$exporter = new MoodleExport($course);
|
$exporter = new MoodleExport($course);
|
||||||
|
MoodleExport::debugStaticLog('MoodleExport instance created for full export');
|
||||||
|
|
||||||
$exporter->setAdminUserData($adminId, $adminUsername, $adminEmail);
|
$exporter->setAdminUserData($adminId, $adminUsername, $adminEmail);
|
||||||
|
MoodleExport::debugStaticLog('Admin user data configured', [
|
||||||
|
'admin_id' => $adminId,
|
||||||
|
'admin_username' => $adminUsername,
|
||||||
|
'admin_email' => $adminEmail,
|
||||||
|
]);
|
||||||
|
|
||||||
$courseId = api_get_course_id(); // Get course ID
|
$courseId = api_get_course_id(); // Get course ID
|
||||||
$exportDir = 'moodle_export_'.$courseId;
|
$exportDir = 'moodle_export_'.$courseId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$moodleVersion = $values['moodle_version'] ?? '3';
|
$moodleVersion = isset($values['moodle_version']) ? (int) $values['moodle_version'] : 3;
|
||||||
|
MoodleExport::debugStaticLog('Starting full Moodle export', [
|
||||||
|
'course_id' => $courseId,
|
||||||
|
'export_dir' => $exportDir,
|
||||||
|
'moodle_version' => $moodleVersion,
|
||||||
|
]);
|
||||||
|
|
||||||
$mbzFile = $exporter->export($courseId, $exportDir, $moodleVersion);
|
$mbzFile = $exporter->export($courseId, $exportDir, $moodleVersion);
|
||||||
|
MoodleExport::debugStaticLog('Full Moodle export finished', [
|
||||||
|
'mbz_file' => $mbzFile,
|
||||||
|
]);
|
||||||
|
|
||||||
echo Display::return_message(get_lang('MoodleExportCreated'), 'confirm');
|
echo Display::return_message(get_lang('MoodleExportCreated'), 'confirm');
|
||||||
echo '<br />';
|
echo '<br />';
|
||||||
echo Display::url(
|
echo Display::url(
|
||||||
@@ -147,14 +240,25 @@ if ($action === 'course_select_form' && Security::check_token('post')) {
|
|||||||
api_get_path(WEB_CODE_PATH).'course_info/download.php?archive_path=1&archive='.basename($mbzFile).'&'.api_get_cidreq(),
|
api_get_path(WEB_CODE_PATH).'course_info/download.php?archive_path=1&archive='.basename($mbzFile).'&'.api_get_cidreq(),
|
||||||
['class' => 'btn btn-primary btn-large']
|
['class' => 'btn btn-primary btn-large']
|
||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
} catch (Throwable $e) {
|
||||||
|
if ($debugMoodleExport) {
|
||||||
|
error_log('[MoodleExport] Export failed: '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine());
|
||||||
|
error_log('[MoodleExport] Stack trace: '.$e->getTraceAsString());
|
||||||
|
}
|
||||||
|
|
||||||
echo Display::return_message(get_lang('ErrorCreatingExport').': '.$e->getMessage(), 'error');
|
echo Display::return_message(get_lang('ErrorCreatingExport').': '.$e->getMessage(), 'error');
|
||||||
}
|
}
|
||||||
} elseif ($values['export_option'] === 'select_items') {
|
} elseif ($values['export_option'] === 'select_items') {
|
||||||
// Partial export - go to the item selection step
|
// Partial export - go to the item selection step
|
||||||
|
MoodleExport::debugStaticLog('Select items selected, building partial course for resource selection form');
|
||||||
$cb = new CourseBuilder('partial');
|
$cb = new CourseBuilder('partial');
|
||||||
$course = $cb->build();
|
$course = $cb->build();
|
||||||
|
MoodleExport::restoreMainDatabaseConnection();
|
||||||
|
MoodleExport::debugStaticLog('Partial course built for resource selection form');
|
||||||
|
|
||||||
if ($course->has_resources()) {
|
if ($course->has_resources()) {
|
||||||
|
MoodleExport::debugStaticLog('Partial course has resources, rendering resource selection form');
|
||||||
|
|
||||||
// Add token to Course select form
|
// Add token to Course select form
|
||||||
$hiddenFields['sec_token'] = Security::get_token();
|
$hiddenFields['sec_token'] = Security::get_token();
|
||||||
$hiddenFields['admin_id'] = $adminId;
|
$hiddenFields['admin_id'] = $adminId;
|
||||||
@@ -162,18 +266,27 @@ if ($action === 'course_select_form' && Security::check_token('post')) {
|
|||||||
$hiddenFields['admin_email'] = $adminEmail;
|
$hiddenFields['admin_email'] = $adminEmail;
|
||||||
|
|
||||||
CourseSelectForm::display_form($course, $hiddenFields, false, true);
|
CourseSelectForm::display_form($course, $hiddenFields, false, true);
|
||||||
|
MoodleExport::debugStaticLog('Resource selection form rendered');
|
||||||
} else {
|
} else {
|
||||||
|
MoodleExport::debugStaticLog('Partial course has no resources');
|
||||||
echo Display::return_message(get_lang('NoResourcesToExport'), 'warning');
|
echo Display::return_message(get_lang('NoResourcesToExport'), 'warning');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
MoodleExport::debugStaticLog('Initial export form not submitted or not valid, rendering form');
|
||||||
|
|
||||||
echo '<div class="row">';
|
echo '<div class="row">';
|
||||||
echo '<div class="col-md-12">';
|
echo '<div class="col-md-12">';
|
||||||
echo '<div class="tool-export">';
|
echo '<div class="tool-export">';
|
||||||
|
MoodleExport::debugStaticLog('Initial export form display started');
|
||||||
$form->display();
|
$form->display();
|
||||||
|
MoodleExport::debugStaticLog('Initial export form display finished');
|
||||||
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MoodleExport::debugStaticLog('Displaying page footer');
|
||||||
Display::display_footer();
|
Display::display_footer();
|
||||||
|
MoodleExport::debugStaticLog('Page footer displayed');
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ foreach ($langs as $lang) {
|
|||||||
$out = [];
|
$out = [];
|
||||||
if (is_file($file)) {
|
if (is_file($file)) {
|
||||||
//$terms = array_merge($terms,SubLanguageManager::get_all_language_variable_in_file($file,true));
|
//$terms = array_merge($terms,SubLanguageManager::get_all_language_variable_in_file($file,true));
|
||||||
@exec('php -l '.$file, $out);
|
@exec('php -l '.escapeshellarg($file), $out);
|
||||||
if (substr($out[0], 0, 2) != 'No') {
|
if (substr($out[0], 0, 2) != 'No') {
|
||||||
echo $out[0]."\n";
|
echo $out[0]."\n";
|
||||||
$ok = false;
|
$ok = false;
|
||||||
|
|||||||
@@ -1094,8 +1094,8 @@ function store_add_dropbox($file = [], $work = null)
|
|||||||
$dropbox_filename = add_ext_on_mime($dropbox_filename, $dropbox_filetype);
|
$dropbox_filename = add_ext_on_mime($dropbox_filename, $dropbox_filetype);
|
||||||
// Replace dangerous characters
|
// Replace dangerous characters
|
||||||
$dropbox_filename = api_replace_dangerous_char($dropbox_filename);
|
$dropbox_filename = api_replace_dangerous_char($dropbox_filename);
|
||||||
// Transform any .php file in .phps fo security
|
// Transform any .php file in .phps and any .htaccess in htaccess.txt for security
|
||||||
$dropbox_filename = php2phps($dropbox_filename);
|
$dropbox_filename = disable_dangerous_file($dropbox_filename);
|
||||||
|
|
||||||
//filter extension
|
//filter extension
|
||||||
if (!filter_extension($dropbox_filename)) {
|
if (!filter_extension($dropbox_filename)) {
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
/* For licensing terms, see /license.txt */
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
|
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
|
||||||
use Symfony\Component\DomCrawler\Crawler;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
|
* @copyright (c) 2001-2006 Universite catholique de Louvain (UCL)
|
||||||
@@ -350,7 +349,7 @@ function parseQti2($xmlData)
|
|||||||
global $questionTempDir;
|
global $questionTempDir;
|
||||||
global $resourcesLinks;
|
global $resourcesLinks;
|
||||||
|
|
||||||
$crawler = new Crawler($xmlData);
|
$crawler = Import::xmlFromString($xmlData);
|
||||||
$nodes = $crawler->filter('*');
|
$nodes = $crawler->filter('*');
|
||||||
|
|
||||||
$currentQuestionIdent = '';
|
$currentQuestionIdent = '';
|
||||||
|
|||||||
+100
-85
@@ -44,7 +44,9 @@ function show_image(image,width,height) {
|
|||||||
|
|
||||||
$export = isset($_GET['export']) ? $_GET['export'] : false;
|
$export = isset($_GET['export']) ? $_GET['export'] : false;
|
||||||
$sessionId = isset($_GET['id_session']) ? intval($_GET['id_session']) : 0;
|
$sessionId = isset($_GET['id_session']) ? intval($_GET['id_session']) : 0;
|
||||||
$origin = isset($_GET['origin']) ? Security::remove_XSS($_GET['origin']) : '';
|
// Use the canonical helper, which URL-encodes "origin" so it cannot break out
|
||||||
|
// of the href attribute (XSS) when later reflected into links.
|
||||||
|
$origin = api_get_origin();
|
||||||
$studentId = (int) $_GET['student'];
|
$studentId = (int) $_GET['student'];
|
||||||
$coachId = isset($_GET['id_coach']) ? (int) $_GET['id_coach'] : 0;
|
$coachId = isset($_GET['id_coach']) ? (int) $_GET['id_coach'] : 0;
|
||||||
|
|
||||||
@@ -326,35 +328,48 @@ $token = Security::get_token();
|
|||||||
if (!empty($studentId)) {
|
if (!empty($studentId)) {
|
||||||
// Actions bar
|
// Actions bar
|
||||||
echo '<div class="actions">';
|
echo '<div class="actions">';
|
||||||
echo '<a href="javascript: window.history.go(-1);">'.
|
echo Display::url(
|
||||||
Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM).'</a>';
|
Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM),
|
||||||
|
'javascript: window.history.go(-1);'
|
||||||
|
);
|
||||||
|
|
||||||
echo '<a href="javascript: void(0);" onclick="javascript: window.print();">'.
|
echo Display::url(
|
||||||
Display::return_icon('printer.png', get_lang('Print'), '', ICON_SIZE_MEDIUM).'</a>';
|
Display::return_icon('printer.png', get_lang('Print'), '', ICON_SIZE_MEDIUM),
|
||||||
|
'javascript: void(0);',
|
||||||
|
['onclick' => 'javascript: window.print();']
|
||||||
|
);
|
||||||
|
|
||||||
echo '<a href="'.api_get_self().'?'.Security::remove_XSS($_SERVER['QUERY_STRING']).'&export=csv">'.
|
echo Display::url(
|
||||||
Display::return_icon('export_csv.png', get_lang('ExportAsCSV'), '', ICON_SIZE_MEDIUM).'</a> ';
|
Display::return_icon('export_csv.png', get_lang('ExportAsCSV'), '', ICON_SIZE_MEDIUM),
|
||||||
|
api_get_self().'?'.Security::remove_XSS($_SERVER['QUERY_STRING']).'&export=csv'
|
||||||
|
).' ';
|
||||||
|
|
||||||
echo '<a href="'.api_get_self().'?'.Security::remove_XSS($_SERVER['QUERY_STRING']).'&export=xls">'.
|
echo Display::url(
|
||||||
Display::return_icon('export_excel.png', get_lang('ExportAsXLS'), '', ICON_SIZE_MEDIUM).'</a> ';
|
Display::return_icon('export_excel.png', get_lang('ExportAsXLS'), '', ICON_SIZE_MEDIUM),
|
||||||
|
api_get_self().'?'.Security::remove_XSS($_SERVER['QUERY_STRING']).'&export=xls'
|
||||||
|
).' ';
|
||||||
|
|
||||||
if (!empty($user_info['email'])) {
|
if (!empty($user_info['email'])) {
|
||||||
$send_mail = '<a href="mailto:'.$user_info['email'].'">'.
|
$send_mail = Display::url(
|
||||||
Display::return_icon('mail_send.png', get_lang('SendMail'), '', ICON_SIZE_MEDIUM).'</a>';
|
Display::return_icon('mail_send.png', get_lang('SendMail'), '', ICON_SIZE_MEDIUM),
|
||||||
|
'mailto:'.$user_info['email']
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$send_mail = Display::return_icon('mail_send_na.png', get_lang('SendMail'), '', ICON_SIZE_MEDIUM);
|
$send_mail = Display::return_icon('mail_send_na.png', get_lang('SendMail'), '', ICON_SIZE_MEDIUM);
|
||||||
}
|
}
|
||||||
echo $send_mail;
|
echo $send_mail;
|
||||||
if (!empty($studentId) && !empty($course_code)) {
|
if (!empty($studentId) && !empty($course_code)) {
|
||||||
// Only show link to connection details if course and student were defined in the URL
|
// Only show link to connection details if course and student were defined in the URL
|
||||||
echo '<a href="access_details.php?student='.$studentId.'&course='.$course_code.'&origin='.$origin.'&cidReq='.$course_code.'&id_session='.$sessionId.'">'.
|
echo Display::url(
|
||||||
Display::return_icon('statistics.png', get_lang('AccessDetails'), '', ICON_SIZE_MEDIUM).'</a>';
|
Display::return_icon('statistics.png', get_lang('AccessDetails'), '', ICON_SIZE_MEDIUM),
|
||||||
|
'access_details.php?student='.$studentId.'&course='.$course_code.'&origin='.$origin.'&cidReq='.$course_code.'&id_session='.$sessionId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (api_can_login_as($studentId)) {
|
if (api_can_login_as($studentId)) {
|
||||||
echo '<a href="'.api_get_path(
|
echo Display::url(
|
||||||
WEB_CODE_PATH
|
Display::return_icon('login_as.png', get_lang('LoginAs'), null, ICON_SIZE_MEDIUM),
|
||||||
).'admin/user_list.php?action=login_as&user_id='.$studentId.'&sec_token='.$token.'">'.
|
api_get_path(WEB_CODE_PATH).'admin/user_list.php?action=login_as&user_id='.$studentId.'&sec_token='.$token
|
||||||
Display::return_icon('login_as.png', get_lang('LoginAs'), null, ICON_SIZE_MEDIUM).'</a> ';
|
).' ';
|
||||||
}
|
}
|
||||||
|
|
||||||
echo Display::url(
|
echo Display::url(
|
||||||
@@ -478,7 +493,7 @@ if (!empty($studentId)) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td><?php echo get_lang('Email').' : ';
|
<td><?php echo get_lang('Email').' : ';
|
||||||
if (!empty($user_info['email'])) {
|
if (!empty($user_info['email'])) {
|
||||||
echo '<a href="mailto:'.$user_info['email'].'">'.$user_info['email'].'</a>';
|
echo Display::url($user_info['email'], 'mailto:'.$user_info['email']);
|
||||||
} else {
|
} else {
|
||||||
echo get_lang('NoEmail');
|
echo get_lang('NoEmail');
|
||||||
} ?>
|
} ?>
|
||||||
@@ -770,8 +785,10 @@ if (!empty($studentId)) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
echo '<tr>
|
echo '<tr>
|
||||||
<td ><a href="'.$courseInfoItem['course_public_url'].'?id_session='.$sId.'">'.
|
<td >'.Display::url(
|
||||||
$courseInfoItem['title'].'</a></td>
|
$courseInfoItem['title'],
|
||||||
|
$courseInfoItem['course_public_url'].'?id_session='.$sId
|
||||||
|
).'</td>
|
||||||
<td >'.$time_spent_on_course.'</td>
|
<td >'.$time_spent_on_course.'</td>
|
||||||
<td >'.$progress.'</td>
|
<td >'.$progress.'</td>
|
||||||
<td >'.$score.'</td>
|
<td >'.$score.'</td>
|
||||||
@@ -779,14 +796,15 @@ if (!empty($studentId)) {
|
|||||||
<td >'.$scoretotal_display.'</td>';
|
<td >'.$scoretotal_display.'</td>';
|
||||||
|
|
||||||
if (!empty($coachId)) {
|
if (!empty($coachId)) {
|
||||||
echo '<td width="10"><a href="'.api_get_self().
|
echo '<td width="10">'.Display::url(
|
||||||
'?student='.$user_info['user_id'].'&details=true&course='.$courseInfoItem['code'].'&id_coach='.$coachId.
|
Display::return_icon('2rightarrow.png', get_lang('Details')),
|
||||||
'&origin='.$origin.'&id_session='.$sId.'#infosStudent">
|
api_get_self().'?student='.$user_info['user_id'].'&details=true&course='.$courseInfoItem['code'].'&id_coach='.$coachId.'&origin='.$origin.'&id_session='.$sId.'#infosStudent'
|
||||||
'.Display::return_icon('2rightarrow.png', get_lang('Details')).'</a></td>';
|
).'</td>';
|
||||||
} else {
|
} else {
|
||||||
echo '<td width="10"><a href="'.api_get_self(
|
echo '<td width="10">'.Display::url(
|
||||||
).'?student='.$user_info['user_id'].'&details=true&course='.$courseInfoItem['code'].'&origin='.$origin.'&id_session='.$sId.'#infosStudent">
|
Display::return_icon('2rightarrow.png', get_lang('Details')),
|
||||||
'.Display::return_icon('2rightarrow.png', get_lang('Details')).'</a></td>';
|
api_get_self().'?student='.$user_info['user_id'].'&details=true&course='.$courseInfoItem['code'].'&origin='.$origin.'&id_session='.$sId.'#infosStudent'
|
||||||
|
).'</td>';
|
||||||
}
|
}
|
||||||
echo '</tr>';
|
echo '</tr>';
|
||||||
}
|
}
|
||||||
@@ -1033,21 +1051,22 @@ if (!empty($studentId)) {
|
|||||||
if (api_is_allowed_to_edit()) {
|
if (api_is_allowed_to_edit()) {
|
||||||
echo '<td>';
|
echo '<td>';
|
||||||
if ($any_result === true) {
|
if ($any_result === true) {
|
||||||
echo '<a href="myStudents.php?action=reset_lp&sec_token='.$token.
|
echo Display::url(
|
||||||
|
Display::return_icon(
|
||||||
|
'clean.png',
|
||||||
|
get_lang('Clean'),
|
||||||
|
'',
|
||||||
|
ICON_SIZE_SMALL
|
||||||
|
),
|
||||||
|
'myStudents.php?action=reset_lp&sec_token='.$token.
|
||||||
'&cidReq='.$course_code.
|
'&cidReq='.$course_code.
|
||||||
'&course='.$course_code.
|
'&course='.$course_code.
|
||||||
'&details='.Security::remove_XSS($_GET['details']).
|
'&details='.Security::remove_XSS($_GET['details']).
|
||||||
'&origin='.$origin.
|
'&origin='.$origin.
|
||||||
'&lp_id='.$learnpath->getId().
|
'&lp_id='.$learnpath->getId().
|
||||||
'&student='.$user_info['user_id'].
|
'&student='.$user_info['user_id'].
|
||||||
'&details=true&id_session='.$sessionId.'">';
|
'&details=true&id_session='.$sessionId
|
||||||
echo Display::return_icon(
|
);
|
||||||
'clean.png',
|
|
||||||
get_lang('Clean'),
|
|
||||||
'',
|
|
||||||
ICON_SIZE_SMALL
|
|
||||||
).'</a>';
|
|
||||||
echo '</a>';
|
|
||||||
}
|
}
|
||||||
echo '</td>';
|
echo '</td>';
|
||||||
echo '</tr>';
|
echo '</tr>';
|
||||||
@@ -1186,14 +1205,14 @@ if (!empty($studentId)) {
|
|||||||
if (Database::num_rows($result_last_attempt) > 0) {
|
if (Database::num_rows($result_last_attempt) > 0) {
|
||||||
$id_last_attempt = Database::result($result_last_attempt, 0, 0);
|
$id_last_attempt = Database::result($result_last_attempt, 0, 0);
|
||||||
if ($count_attempts > 0) {
|
if ($count_attempts > 0) {
|
||||||
echo '<a href="../exercise/exercise_show.php?id='.$id_last_attempt.
|
echo Display::url(
|
||||||
|
Display::return_icon('quiz.gif'),
|
||||||
|
'../exercise/exercise_show.php?id='.$id_last_attempt.
|
||||||
'&cidReq='.$course_code.
|
'&cidReq='.$course_code.
|
||||||
'&session_id='.$sessionId.
|
'&session_id='.$sessionId.
|
||||||
'&student='.$studentId.
|
'&student='.$studentId.
|
||||||
'&origin='.(empty($origin) ? 'tracking' : $origin).
|
'&origin='.(empty($origin) ? 'tracking' : $origin)
|
||||||
'">';
|
);
|
||||||
echo Display::return_icon('quiz.gif');
|
|
||||||
echo '</a>';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo '</td>';
|
echo '</td>';
|
||||||
@@ -1690,14 +1709,17 @@ if ($end_date < '2010-01-01') {
|
|||||||
";
|
";
|
||||||
$exe_id = $a_inter['exe_id']; ?>
|
$exe_id = $a_inter['exe_id']; ?>
|
||||||
<td class="highlight">
|
<td class="highlight">
|
||||||
<a href="edit_intervention.php?num=<?php echo $exe_id; ?>&student_id=<?php echo $studentId; ?> ">
|
<?php
|
||||||
<?php echo Display::return_icon('edit.png', get_lang('Edit')); ?></a>
|
echo Display::url(
|
||||||
<a
|
Display::return_icon('edit.png', get_lang('Edit')),
|
||||||
href="delete_exam.php?num=<?php echo $exe_id; ?>&student_id=<?php echo $studentId; ?>">
|
'edit_intervention.php?num='.$exe_id.'&student_id='.$studentId
|
||||||
<img
|
);
|
||||||
src="../img/delete.gif" border="0"
|
echo ' ';
|
||||||
onClick='return confirmDelete2()'>
|
echo Display::url(
|
||||||
</a>
|
Display::img('../img/delete.gif', get_lang('Delete'), ['border' => '0', 'onclick' => 'return confirmDelete2()']),
|
||||||
|
'delete_exam.php?num='.$exe_id.'&student_id='.$studentId
|
||||||
|
);
|
||||||
|
?>
|
||||||
<?php
|
<?php
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
@@ -1925,16 +1947,15 @@ if (empty($_GET['details'])) {
|
|||||||
<td >'.$progress.'</td>
|
<td >'.$progress.'</td>
|
||||||
<td >'.$end_date_module.'</td>';
|
<td >'.$end_date_module.'</td>';
|
||||||
if (!empty($coachId)) {
|
if (!empty($coachId)) {
|
||||||
echo '<td width="10">
|
echo '<td width="10">'.Display::url(
|
||||||
<a href="'.api_get_self().'?student='.$user_info['user_id'].'&details=true&course='.$course_info['code'].'&id_coach='.$coachId.
|
Display::return_icon('2rightarrow.png', get_lang('Details')),
|
||||||
'&origin='.Security::remove_XSS($_GET['origin']).'&id_session='.$session_id.'#infosStudent">'.
|
api_get_self().'?student='.$user_info['user_id'].'&details=true&course='.$course_info['code'].'&id_coach='.$coachId.'&origin='.$origin.'&id_session='.$session_id.'#infosStudent'
|
||||||
Display::return_icon('2rightarrow.png', get_lang('Details')).'</a></td>';
|
).'</td>';
|
||||||
} else {
|
} else {
|
||||||
echo '<td width="10"><a href="'.api_get_self(
|
echo '<td width="10">'.Display::url(
|
||||||
).'?student='.$user_info['user_id'].'&details=true&course='.$course_info['code'].'&origin='.Security::remove_XSS(
|
Display::return_icon('2rightarrow.png', get_lang('Details')),
|
||||||
$_GET['origin']
|
api_get_self().'?student='.$user_info['user_id'].'&details=true&course='.$course_info['code'].'&origin='.$origin.'&id_session='.$session_id.'#infosStudent'
|
||||||
).'&id_session='.$session_id.'#infosStudent">'.
|
).'</td>';
|
||||||
Display::return_icon('2rightarrow.png', get_lang('Details')).'</a></td>';
|
|
||||||
}
|
}
|
||||||
echo '</tr>';
|
echo '</tr>';
|
||||||
}
|
}
|
||||||
@@ -2152,12 +2173,8 @@ if (empty($_GET['details'])) {
|
|||||||
$from = '&from=myspace';
|
$from = '&from=myspace';
|
||||||
}
|
}
|
||||||
$link = Display::url(
|
$link = Display::url(
|
||||||
Display::return_icon('2rightarrow.png', get_lang('Details')),
|
Display::return_icon('2rightarrow.png', get_lang('Details')),
|
||||||
'lp_tracking.php?cidReq='.Security::remove_XSS($_GET['course']).'&course='.Security::remove_XSS(
|
'lp_tracking.php?cidReq='.$course_code.'&course='.$course_code.$from.'&origin='.$origin.'&lp_id='.$learnpath['id'].'&student_id='.$user_info['user_id'].'&id_session='.$session_id
|
||||||
$_GET['course']
|
|
||||||
).$from.'&origin='.Security::remove_XSS(
|
|
||||||
$_GET['origin']
|
|
||||||
).'&lp_id='.$learnpath['id'].'&student_id='.$user_info['user_id'].'&id_session='.$session_id
|
|
||||||
);
|
);
|
||||||
echo Display::tag('td', $link);
|
echo Display::tag('td', $link);
|
||||||
}
|
}
|
||||||
@@ -2165,17 +2182,10 @@ if (empty($_GET['details'])) {
|
|||||||
if (api_is_allowed_to_edit()) {
|
if (api_is_allowed_to_edit()) {
|
||||||
echo '<td>';
|
echo '<td>';
|
||||||
if ($any_result === true) {
|
if ($any_result === true) {
|
||||||
echo '<a href="myStudents.php?action=reset_lp&sec_token='.$token.'&cidReq='.Security::remove_XSS(
|
echo Display::url(
|
||||||
$_GET['course']
|
Display::return_icon('clean.png', get_lang('Clean'), '', ICON_SIZE_SMALL),
|
||||||
).'&course='.Security::remove_XSS($_GET['course']).'&details='.Security::remove_XSS(
|
'myStudents.php?action=reset_lp&sec_token='.$token.'&cidReq='.$course_code.'&course='.$course_code.'&details='.Security::remove_XSS($_GET['details']).'&origin='.$origin.'&lp_id='.$learnpath['id'].'&student='.$user_info['user_id'].'&details=true&id_session='.$session_id
|
||||||
$_GET['details']
|
);
|
||||||
).'&origin='.Security::remove_XSS(
|
|
||||||
$_GET['origin']
|
|
||||||
).'&lp_id='.$learnpath['id'].'&student='.$user_info['user_id'].'&details=true&id_session='.Security::remove_XSS(
|
|
||||||
$_GET['id_session']
|
|
||||||
).'">';
|
|
||||||
echo Display::return_icon('clean.png', get_lang('Clean'), '', ICON_SIZE_SMALL).'</a>';
|
|
||||||
echo '</a>';
|
|
||||||
}
|
}
|
||||||
echo '</td>';
|
echo '</td>';
|
||||||
echo '</tr>';
|
echo '</tr>';
|
||||||
@@ -2297,9 +2307,10 @@ if (empty($_GET['details'])) {
|
|||||||
if (Database::num_rows($result_last_attempt) > 0) {
|
if (Database::num_rows($result_last_attempt) > 0) {
|
||||||
$id_last_attempt = Database::result($result_last_attempt, 0, 0);
|
$id_last_attempt = Database::result($result_last_attempt, 0, 0);
|
||||||
if ($count_attempts > 0) {
|
if ($count_attempts > 0) {
|
||||||
echo '<a href="../exercice/exercise_show.php?id='.$id_last_attempt.'&cidReq='.$course_code.'&session_id='.$session_id.'&student='.$studentId.'&origin='.(empty($_GET['origin']) ? 'tracking' : Security::remove_XSS(
|
echo Display::url(
|
||||||
$_GET['origin']
|
Display::return_icon('quiz.gif'),
|
||||||
)).'"> <img src="'.api_get_path(WEB_IMG_PATH).'quiz.gif" border="0" /> </a>';
|
'../exercice/exercise_show.php?id='.$id_last_attempt.'&cidReq='.$course_code.'&session_id='.$session_id.'&student='.$studentId.'&origin='.(empty($origin) ? 'tracking' : $origin)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
echo '</td>';
|
echo '</td>';
|
||||||
@@ -2572,13 +2583,17 @@ if ($export_csv) {
|
|||||||
$exe_idd = $a_exam['exe_id']; ?>
|
$exe_idd = $a_exam['exe_id']; ?>
|
||||||
<INPUT type=hidden name=ex_idd value= <?php echo "$exe_idd"; ?>>
|
<INPUT type=hidden name=ex_idd value= <?php echo "$exe_idd"; ?>>
|
||||||
<td class="highlight">
|
<td class="highlight">
|
||||||
<a href="edit_exam.php?num=<?php echo $exe_idd; ?>&student_id=<?php echo $studentId; ?>">
|
<?php
|
||||||
<?php echo Display::return_icon('edit.png', get_lang('Edit')); ?>
|
echo Display::url(
|
||||||
</a>
|
Display::return_icon('edit.png', get_lang('Edit')),
|
||||||
<a href="delete_exam.php?num=<?php echo $exe_idd; ?>&student_id=<?php echo $studentId; ?>">
|
'edit_exam.php?num='.$exe_idd.'&student_id='.$studentId
|
||||||
<img
|
);
|
||||||
src="../img/delete.gif" border="0"
|
echo ' ';
|
||||||
onClick='return confirmDelete2()'></a>
|
echo Display::url(
|
||||||
|
Display::img('../img/delete.gif', get_lang('Delete'), ['border' => '0', 'onclick' => 'return confirmDelete2()']),
|
||||||
|
'delete_exam.php?num='.$exe_idd.'&student_id='.$studentId
|
||||||
|
);
|
||||||
|
?>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ if ($languageId) {
|
|||||||
$form->addHidden('sub_language_id', $languageInfo['id']);
|
$form->addHidden('sub_language_id', $languageInfo['id']);
|
||||||
$form->addHidden('redirect', true);
|
$form->addHidden('redirect', true);
|
||||||
$form->addHidden('extra_field_type', $extraField->getExtraFieldType());
|
$form->addHidden('extra_field_type', $extraField->getExtraFieldType());
|
||||||
|
$form->addHidden('sec_token', Security::get_token());
|
||||||
$form->addButtonSave(get_lang('Save'));
|
$form->addButtonSave(get_lang('Save'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ if (PHP_SAPI !== 'cli') {
|
|||||||
$courseCode = $argv[1];
|
$courseCode = $argv[1];
|
||||||
$sessionId = $argv[2];
|
$sessionId = $argv[2];
|
||||||
$categoryId = $argv[3];
|
$categoryId = $argv[3];
|
||||||
$userList = isset($argv[4]) ? explode(',', $argv[4]) : [];
|
$userList = !empty($argv[4]) ? explode(',', $argv[4]) : [];
|
||||||
|
|
||||||
$date = api_get_utc_datetime(null, false, true);
|
$date = api_get_utc_datetime(null, false, true);
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ switch ($action) {
|
|||||||
$learner['username'],
|
$learner['username'],
|
||||||
$learner['firstname'],
|
$learner['firstname'],
|
||||||
$learner['lastname'],
|
$learner['lastname'],
|
||||||
|
$learner['email'],
|
||||||
];
|
];
|
||||||
}, $certificate_list);
|
}, $certificate_list);
|
||||||
|
|
||||||
@@ -188,9 +189,11 @@ switch ($action) {
|
|||||||
$csvHeaders[] = get_lang('UserName');
|
$csvHeaders[] = get_lang('UserName');
|
||||||
$csvHeaders[] = get_lang('FirstName');
|
$csvHeaders[] = get_lang('FirstName');
|
||||||
$csvHeaders[] = get_lang('LastName');
|
$csvHeaders[] = get_lang('LastName');
|
||||||
|
$csvHeaders[] = get_lang('Email');
|
||||||
$csvHeaders[] = get_lang('Score');
|
$csvHeaders[] = get_lang('Score');
|
||||||
$csvHeaders[] = get_lang('Date');
|
$csvHeaders[] = get_lang('Date');
|
||||||
|
|
||||||
|
$usersProfileInfo = [];
|
||||||
$extraFields = [];
|
$extraFields = [];
|
||||||
$extraFieldsFromSettings = [];
|
$extraFieldsFromSettings = [];
|
||||||
$extraFieldsFromSettings = api_get_configuration_value('certificate_export_report_user_extra_fields');
|
$extraFieldsFromSettings = api_get_configuration_value('certificate_export_report_user_extra_fields');
|
||||||
@@ -213,24 +216,23 @@ switch ($action) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
foreach ($exportData as $key => $row) {
|
||||||
|
$list = GradebookUtils::get_list_gradebook_certificates_by_user_id(
|
||||||
|
$row[0],
|
||||||
|
$categoryId
|
||||||
|
);
|
||||||
|
|
||||||
foreach ($exportData as $key => $row) {
|
foreach ($list as $valueCertificate) {
|
||||||
$list = GradebookUtils::get_list_gradebook_certificates_by_user_id(
|
$row[] = $valueCertificate['score_certificate'];
|
||||||
$row[0],
|
$row[] = api_convert_and_format_date($valueCertificate['created_at']);
|
||||||
$categoryId
|
|
||||||
);
|
|
||||||
|
|
||||||
foreach ($list as $valueCertificate) {
|
|
||||||
$row[] = $valueCertificate['score_certificate'];
|
|
||||||
$row[] = api_convert_and_format_date($valueCertificate['created_at']);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($usersProfileInfo as $extraInfo) {
|
|
||||||
$row[] = $extraInfo[$row[0]][0];
|
|
||||||
}
|
|
||||||
|
|
||||||
$csvContent[] = $row;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach ($usersProfileInfo as $extraInfo) {
|
||||||
|
$row[] = $extraInfo[$row[0]][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
$csvContent[] = $row;
|
||||||
}
|
}
|
||||||
|
|
||||||
array_unshift($csvContent, $csvHeaders);
|
array_unshift($csvContent, $csvHeaders);
|
||||||
|
|||||||
@@ -56,11 +56,19 @@ switch ($action) {
|
|||||||
exit;
|
exit;
|
||||||
break;
|
break;
|
||||||
case 'lock':
|
case 'lock':
|
||||||
|
if (!Security::check_token('get')) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
}
|
||||||
|
Security::clear_token();
|
||||||
$category_to_lock = Category::load($_GET['category_id']);
|
$category_to_lock = Category::load($_GET['category_id']);
|
||||||
$category_to_lock[0]->lockAllItems(1);
|
$category_to_lock[0]->lockAllItems(1);
|
||||||
$confirmation_message = get_lang('GradebookLockedAlert');
|
$confirmation_message = get_lang('GradebookLockedAlert');
|
||||||
break;
|
break;
|
||||||
case 'unlock':
|
case 'unlock':
|
||||||
|
if (!Security::check_token('get')) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
}
|
||||||
|
Security::clear_token();
|
||||||
if (api_is_platform_admin()) {
|
if (api_is_platform_admin()) {
|
||||||
$category_to_lock = Category::load($_GET['category_id']);
|
$category_to_lock = Category::load($_GET['category_id']);
|
||||||
$category_to_lock[0]->lockAllItems(0);
|
$category_to_lock[0]->lockAllItems(0);
|
||||||
@@ -360,6 +368,10 @@ if (isset($_GET['visiblecat'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_GET['deletecat'])) {
|
if (isset($_GET['deletecat'])) {
|
||||||
|
if (!Security::check_token('get')) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
}
|
||||||
|
Security::clear_token();
|
||||||
GradebookUtils::block_students();
|
GradebookUtils::block_students();
|
||||||
$cats = Category::load($_GET['deletecat']);
|
$cats = Category::load($_GET['deletecat']);
|
||||||
if (isset($cats[0])) {
|
if (isset($cats[0])) {
|
||||||
@@ -397,6 +409,10 @@ if (isset($_GET['visibleeval'])) {
|
|||||||
|
|
||||||
// Parameters for evaluations.
|
// Parameters for evaluations.
|
||||||
if (isset($_GET['lockedeval'])) {
|
if (isset($_GET['lockedeval'])) {
|
||||||
|
if (!Security::check_token('get')) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
}
|
||||||
|
Security::clear_token();
|
||||||
GradebookUtils::block_students();
|
GradebookUtils::block_students();
|
||||||
$locked = (int) $_GET['lockedeval'];
|
$locked = (int) $_GET['lockedeval'];
|
||||||
$type_locked = 1;
|
$type_locked = 1;
|
||||||
@@ -414,6 +430,10 @@ if (isset($_GET['lockedeval'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_GET['deleteeval'])) {
|
if (isset($_GET['deleteeval'])) {
|
||||||
|
if (!Security::check_token('get')) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
}
|
||||||
|
Security::clear_token();
|
||||||
GradebookUtils::block_students();
|
GradebookUtils::block_students();
|
||||||
$eval = Evaluation::load($_GET['deleteeval']);
|
$eval = Evaluation::load($_GET['deleteeval']);
|
||||||
if ($eval[0] != null) {
|
if ($eval[0] != null) {
|
||||||
@@ -446,6 +466,10 @@ if (isset($_GET['visiblelink'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isset($_GET['deletelink'])) {
|
if (isset($_GET['deletelink'])) {
|
||||||
|
if (!Security::check_token('get')) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
}
|
||||||
|
Security::clear_token();
|
||||||
GradebookUtils::block_students();
|
GradebookUtils::block_students();
|
||||||
$get_delete_link = (int) $_GET['deletelink'];
|
$get_delete_link = (int) $_GET['deletelink'];
|
||||||
//fixing #5229
|
//fixing #5229
|
||||||
@@ -495,6 +519,10 @@ if (!empty($course_to_crsind) && !isset($_GET['confirm'])) {
|
|||||||
|
|
||||||
// Actions on the sortabletable.
|
// Actions on the sortabletable.
|
||||||
if (isset($_POST['action'])) {
|
if (isset($_POST['action'])) {
|
||||||
|
if (!Security::check_token('post')) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
}
|
||||||
|
Security::clear_token();
|
||||||
GradebookUtils::block_students();
|
GradebookUtils::block_students();
|
||||||
$number_of_selected_items = count($_POST['id']);
|
$number_of_selected_items = count($_POST['id']);
|
||||||
|
|
||||||
|
|||||||
@@ -263,11 +263,13 @@ class GradebookUtils
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (api_is_allowed_to_edit(null, true)) {
|
if (api_is_allowed_to_edit(null, true)) {
|
||||||
|
// Shared CSRF token for the destructive GET links below.
|
||||||
|
$token = Security::get_existing_token();
|
||||||
// Locking button
|
// Locking button
|
||||||
if (api_get_setting('gradebook_locking_enabled') === 'true') {
|
if (api_get_setting('gradebook_locking_enabled') === 'true') {
|
||||||
if ($cat->is_locked()) {
|
if ($cat->is_locked()) {
|
||||||
if (api_is_platform_admin()) {
|
if (api_is_platform_admin()) {
|
||||||
$modify_icons .= ' <a onclick="javascript:if (!confirm(\''.addslashes(get_lang('ConfirmToUnlockElement')).'\')) return false;" href="'.api_get_self().'?'.api_get_cidreq().'&category_id='.$cat->get_id().'&action=unlock">'.
|
$modify_icons .= ' <a onclick="javascript:if (!confirm(\''.addslashes(get_lang('ConfirmToUnlockElement')).'\')) return false;" href="'.api_get_self().'?'.api_get_cidreq().'&category_id='.$cat->get_id().'&action=unlock&sec_token='.$token.'">'.
|
||||||
Display::return_icon('lock.png', get_lang('UnLockEvaluation'), '', ICON_SIZE_SMALL).'</a>';
|
Display::return_icon('lock.png', get_lang('UnLockEvaluation'), '', ICON_SIZE_SMALL).'</a>';
|
||||||
} else {
|
} else {
|
||||||
$modify_icons .= ' <a href="#">'.
|
$modify_icons .= ' <a href="#">'.
|
||||||
@@ -275,7 +277,7 @@ class GradebookUtils
|
|||||||
}
|
}
|
||||||
$modify_icons .= ' <a href="gradebook_flatview.php?export_pdf=category&selectcat='.$cat->get_id().'" >'.Display::return_icon('pdf.png', get_lang('ExportToPDF'), '', ICON_SIZE_SMALL).'</a>';
|
$modify_icons .= ' <a href="gradebook_flatview.php?export_pdf=category&selectcat='.$cat->get_id().'" >'.Display::return_icon('pdf.png', get_lang('ExportToPDF'), '', ICON_SIZE_SMALL).'</a>';
|
||||||
} else {
|
} else {
|
||||||
$modify_icons .= ' <a onclick="javascript:if (!confirm(\''.addslashes(get_lang('ConfirmToLockElement')).'\')) return false;" href="'.api_get_self().'?'.api_get_cidreq().'&category_id='.$cat->get_id().'&action=lock">'.
|
$modify_icons .= ' <a onclick="javascript:if (!confirm(\''.addslashes(get_lang('ConfirmToLockElement')).'\')) return false;" href="'.api_get_self().'?'.api_get_cidreq().'&category_id='.$cat->get_id().'&action=lock&sec_token='.$token.'">'.
|
||||||
Display::return_icon('unlock.png', get_lang('LockEvaluation'), '', ICON_SIZE_SMALL).'</a>';
|
Display::return_icon('unlock.png', get_lang('LockEvaluation'), '', ICON_SIZE_SMALL).'</a>';
|
||||||
$modify_icons .= ' <a href="#" >'.
|
$modify_icons .= ' <a href="#" >'.
|
||||||
Display::return_icon('pdf_na.png', get_lang('ExportToPDF'), '', ICON_SIZE_SMALL).'</a>';
|
Display::return_icon('pdf_na.png', get_lang('ExportToPDF'), '', ICON_SIZE_SMALL).'</a>';
|
||||||
@@ -332,7 +334,7 @@ class GradebookUtils
|
|||||||
ICON_SIZE_SMALL
|
ICON_SIZE_SMALL
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$modify_icons .= ' <a href="'.api_get_self().'?deletecat='.$cat->get_id().'&selectcat='.$selectcat.'&'.$courseParams.'" onclick="return confirmation();">'.
|
$modify_icons .= ' <a href="'.api_get_self().'?deletecat='.$cat->get_id().'&selectcat='.$selectcat.'&sec_token='.$token.'&'.$courseParams.'" onclick="return confirmation();">'.
|
||||||
Display::return_icon(
|
Display::return_icon(
|
||||||
'delete.png',
|
'delete.png',
|
||||||
get_lang('DeleteAll'),
|
get_lang('DeleteAll'),
|
||||||
@@ -421,7 +423,7 @@ class GradebookUtils
|
|||||||
ICON_SIZE_SMALL
|
ICON_SIZE_SMALL
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$modify_icons .= ' <a href="'.api_get_self().'?deleteeval='.$eval->get_id().'&selectcat='.$selectcat.' &'.$courseParams.'" onclick="return confirmation();">'.
|
$modify_icons .= ' <a href="'.api_get_self().'?deleteeval='.$eval->get_id().'&selectcat='.$selectcat.'&sec_token='.Security::get_existing_token().'&'.$courseParams.'" onclick="return confirmation();">'.
|
||||||
Display::return_icon(
|
Display::return_icon(
|
||||||
'delete.png',
|
'delete.png',
|
||||||
get_lang('Delete'),
|
get_lang('Delete'),
|
||||||
@@ -518,7 +520,7 @@ class GradebookUtils
|
|||||||
} else {
|
} else {
|
||||||
$modify_icons .= '
|
$modify_icons .= '
|
||||||
<a
|
<a
|
||||||
href="'.api_get_self().'?deletelink='.$link->get_id().'&selectcat='.$selectcat.' &'.$courseParams.'"
|
href="'.api_get_self().'?deletelink='.$link->get_id().'&selectcat='.$selectcat.'&sec_token='.Security::get_existing_token().'&'.$courseParams.'"
|
||||||
onclick="return confirmation();">'.
|
onclick="return confirmation();">'.
|
||||||
Display::return_icon(
|
Display::return_icon(
|
||||||
'delete.png',
|
'delete.png',
|
||||||
@@ -743,7 +745,7 @@ class GradebookUtils
|
|||||||
{
|
{
|
||||||
$table_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
|
$table_certificate = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
|
||||||
$table_user = Database::get_main_table(TABLE_MAIN_USER);
|
$table_user = Database::get_main_table(TABLE_MAIN_USER);
|
||||||
$sql = 'SELECT DISTINCT u.user_id, u.lastname, u.firstname, u.username, gc.created_at
|
$sql = 'SELECT DISTINCT u.user_id, u.lastname, u.firstname, u.username, u.email, gc.created_at
|
||||||
FROM '.$table_user.' u
|
FROM '.$table_user.' u
|
||||||
INNER JOIN '.$table_certificate.' gc
|
INNER JOIN '.$table_certificate.' gc
|
||||||
ON u.user_id=gc.user_id ';
|
ON u.user_id=gc.user_id ';
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class CatForm extends FormValidator
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->protect();
|
||||||
$this->setDefaults();
|
$this->setDefaults();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ class DataForm extends FormValidator
|
|||||||
$this->form_type = $form_type;
|
$this->form_type = $form_type;
|
||||||
if ($this->form_type == self::TYPE_IMPORT) {
|
if ($this->form_type == self::TYPE_IMPORT) {
|
||||||
$this->build_import_form();
|
$this->build_import_form();
|
||||||
|
// Only the import is state-changing (overwrites scores). The export
|
||||||
|
// branches call validate() twice on the same request, which is
|
||||||
|
// incompatible with protect() (validate() clears the token), so they
|
||||||
|
// are intentionally left without CSRF protection here.
|
||||||
|
$this->protect();
|
||||||
} elseif ($this->form_type == self::TYPE_EXPORT) {
|
} elseif ($this->form_type == self::TYPE_EXPORT) {
|
||||||
if ($locked_status == 0) {
|
if ($locked_status == 0) {
|
||||||
$this->build_export_form_option(false);
|
$this->build_export_form_option(false);
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ class LinkAddEditForm extends FormValidator
|
|||||||
$defaults['visible'] = $visibility_default;
|
$defaults['visible'] = $visibility_default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$this->protect();
|
||||||
// set default values
|
// set default values
|
||||||
$this->setDefaults($defaults);
|
$this->setDefaults($defaults);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class LinkForm extends FormValidator
|
|||||||
} elseif (self::TYPE_MOVE == $form_type) {
|
} elseif (self::TYPE_MOVE == $form_type) {
|
||||||
$this->build_move();
|
$this->build_move();
|
||||||
}
|
}
|
||||||
|
$this->protect();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function build_move()
|
protected function build_move()
|
||||||
|
|||||||
@@ -151,6 +151,7 @@ class ScoreDisplayForm extends FormValidator
|
|||||||
if ($displayscore->is_custom()) {
|
if ($displayscore->is_custom()) {
|
||||||
$this->addButtonSave(get_lang('Ok'));
|
$this->addButtonSave(get_lang('Ok'));
|
||||||
}
|
}
|
||||||
|
$this->protect();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validate()
|
public function validate()
|
||||||
|
|||||||
@@ -80,14 +80,19 @@ switch ($action) {
|
|||||||
|
|
||||||
$userList = implode(',', $userList);
|
$userList = implode(',', $userList);
|
||||||
|
|
||||||
shell_exec(sprintf(
|
$command = sprintf(
|
||||||
"php %s %s %s %s %s > /dev/null &",
|
"php %s %s %s %s",
|
||||||
escapeshellarg($commandScript),
|
escapeshellarg($commandScript),
|
||||||
escapeshellarg($courseCode),
|
escapeshellarg($courseCode),
|
||||||
escapeshellarg((string) $sessionId),
|
escapeshellarg((string) $sessionId),
|
||||||
escapeshellarg((string) $categoryId),
|
escapeshellarg((string) $categoryId)
|
||||||
escapeshellarg($userList)
|
);
|
||||||
));
|
|
||||||
|
if ('' !== $userList) {
|
||||||
|
$command .= ' '.escapeshellarg($userList);
|
||||||
|
}
|
||||||
|
|
||||||
|
shell_exec($command.' > /dev/null &');
|
||||||
break;
|
break;
|
||||||
case 'verify_export_all_certificates':
|
case 'verify_export_all_certificates':
|
||||||
$categoryId = (int) $_GET['cat_id'];
|
$categoryId = (int) $_GET['cat_id'];
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ switch ($action) {
|
|||||||
echo api_get_language_translate_html();
|
echo api_get_language_translate_html();
|
||||||
break;
|
break;
|
||||||
case 'translate_portfolio_category':
|
case 'translate_portfolio_category':
|
||||||
|
// Only platform admins may write to language files
|
||||||
|
if (!api_is_platform_admin()) {
|
||||||
|
api_not_allowed(true);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
if (false === Security::check_token('get')) {
|
if (false === Security::check_token('get')) {
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -31,6 +36,11 @@ switch ($action) {
|
|||||||
$languageId = (int) $_REQUEST['id'];
|
$languageId = (int) $_REQUEST['id'];
|
||||||
$subLanguageId = (int) $_REQUEST['sub'];
|
$subLanguageId = (int) $_REQUEST['sub'];
|
||||||
|
|
||||||
|
// Validate variable name is a safe PHP identifier
|
||||||
|
if (!SubLanguageManager::isValidLanguageVariable($langVariable)) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$langFilesToLoad = SubLanguageManager::get_lang_folder_files_list(
|
$langFilesToLoad = SubLanguageManager::get_lang_folder_files_list(
|
||||||
api_get_path(SYS_LANG_PATH).'english',
|
api_get_path(SYS_LANG_PATH).'english',
|
||||||
true
|
true
|
||||||
@@ -39,6 +49,12 @@ switch ($action) {
|
|||||||
$fileLanguage = $langFilesToLoad[0].'.inc.php';
|
$fileLanguage = $langFilesToLoad[0].'.inc.php';
|
||||||
$allDataOfLanguage = SubLanguageManager::get_all_information_of_sub_language($languageId, $subLanguageId);
|
$allDataOfLanguage = SubLanguageManager::get_all_information_of_sub_language($languageId, $subLanguageId);
|
||||||
|
|
||||||
|
if (empty($allDataOfLanguage) ||
|
||||||
|
!SubLanguageManager::isValidLanguageFolderName($allDataOfLanguage['dokeos_folder'])
|
||||||
|
) {
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
$pathFolder = api_get_path(SYS_LANG_PATH).$allDataOfLanguage['dokeos_folder'].'/'.$fileLanguage;
|
$pathFolder = api_get_path(SYS_LANG_PATH).$allDataOfLanguage['dokeos_folder'].'/'.$fileLanguage;
|
||||||
$allFileOfDirectory = SubLanguageManager::get_all_language_variable_in_file($pathFolder);
|
$allFileOfDirectory = SubLanguageManager::get_all_language_variable_in_file($pathFolder);
|
||||||
$returnValue = SubLanguageManager::add_file_in_language_directory($pathFolder);
|
$returnValue = SubLanguageManager::add_file_in_language_directory($pathFolder);
|
||||||
|
|||||||
@@ -166,6 +166,11 @@ if (($search || $forceSearch) && ($search !== 'false')) {
|
|||||||
$filters = json_decode($_REQUEST['filters2']);
|
$filters = json_decode($_REQUEST['filters2']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!empty($filters) && isset($filters->groupOp)) {
|
||||||
|
$op = strtoupper((string) $filters->groupOp);
|
||||||
|
$filters->groupOp = in_array($op, ['AND', 'OR'], true) ? $op : 'AND';
|
||||||
|
}
|
||||||
|
|
||||||
if (!empty($filters)) {
|
if (!empty($filters)) {
|
||||||
if (in_array($action,
|
if (in_array($action,
|
||||||
[
|
[
|
||||||
@@ -565,10 +570,15 @@ switch ($action) {
|
|||||||
break;
|
break;
|
||||||
case 'get_work_pending_list':
|
case 'get_work_pending_list':
|
||||||
require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
|
require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
|
||||||
$courseId = $_REQUEST['course'] ?? 0;
|
$courseId = (int) ($_REQUEST['course'] ?? 0);
|
||||||
$status = $_REQUEST['status'] ?? 0;
|
$status = $_REQUEST['status'] ?? 0;
|
||||||
if (isset($_REQUEST['work_parent_ids'])) {
|
if (isset($_REQUEST['work_parent_ids'])) {
|
||||||
$whereCondition = ' parent_id IN('.Security::remove_XSS($_REQUEST['work_parent_ids']).')';
|
$workParentIds = array_filter(
|
||||||
|
array_map('intval', explode(',', (string) $_REQUEST['work_parent_ids']))
|
||||||
|
);
|
||||||
|
if (!empty($workParentIds)) {
|
||||||
|
$whereCondition = ' parent_id IN('.implode(',', $workParentIds).')';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$count = getAllWork(
|
$count = getAllWork(
|
||||||
null,
|
null,
|
||||||
@@ -646,7 +656,7 @@ switch ($action) {
|
|||||||
}
|
}
|
||||||
$search_start_date = isset($_REQUEST['start_date']) && !empty($_REQUEST['start_date']) ? $_REQUEST['start_date'] : null;
|
$search_start_date = isset($_REQUEST['start_date']) && !empty($_REQUEST['start_date']) ? $_REQUEST['start_date'] : null;
|
||||||
$search_end_date = isset($_REQUEST['end_date']) && !empty($_REQUEST['end_date']) ? $_REQUEST['end_date'] : null;
|
$search_end_date = isset($_REQUEST['end_date']) && !empty($_REQUEST['end_date']) ? $_REQUEST['end_date'] : null;
|
||||||
$courseId = $_REQUEST['course_id'] ?? 0;
|
$courseId = (int) ($_REQUEST['course_id'] ?? 0);
|
||||||
$exerciseId = $_REQUEST['exercise_id'] ?? 0;
|
$exerciseId = $_REQUEST['exercise_id'] ?? 0;
|
||||||
$status = $_REQUEST['status'] ?? 0;
|
$status = $_REQUEST['status'] ?? 0;
|
||||||
$questionType = $_REQUEST['questionType'] ?? 0;
|
$questionType = $_REQUEST['questionType'] ?? 0;
|
||||||
|
|||||||
@@ -36,9 +36,46 @@ if (!isset($_FILES['audio_blob'], $_REQUEST['audio_dir'])) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$file = isset($_FILES['audio_blob']) ? $_FILES['audio_blob'] : [];
|
$file = isset($_FILES['audio_blob']) ? $_FILES['audio_blob'] : [];
|
||||||
|
|
||||||
|
// Only allow audio uploads: sanitize the filename and reject any non-audio
|
||||||
|
// extension (e.g. .html) so the result cannot be served as HTML/JS by
|
||||||
|
// document.php (stored XSS). The RecordRTC StereoAudioRecorder only ever
|
||||||
|
// produces WAV, so .wav is the single legitimate extension here.
|
||||||
|
$fileName = disable_dangerous_file(api_replace_dangerous_char($file['name']));
|
||||||
|
$fileExtension = strtolower(pathinfo($fileName, PATHINFO_EXTENSION));
|
||||||
|
$allowedAudioExtensions = ['wav'];
|
||||||
|
if (!in_array($fileExtension, $allowedAudioExtensions, true)) {
|
||||||
|
if ($tool === 'exercise') {
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
echo json_encode([
|
||||||
|
'error' => true,
|
||||||
|
'message' => Display::return_message(get_lang('UploadError'), 'error'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
Display::cleanFlashMessages();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display::addFlash(Display::return_message(get_lang('UploadError'), 'error'));
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$file['name'] = $fileName;
|
||||||
$file['file'] = $file;
|
$file['file'] = $file;
|
||||||
$audioDir = Security::remove_XSS($_REQUEST['audio_dir']);
|
$audioDir = Security::remove_XSS($_REQUEST['audio_dir']);
|
||||||
|
|
||||||
|
// Neutralize path traversal: audio_dir must be a relative path inside the
|
||||||
|
// course document tree, so drop any traversal or absolute-path segments
|
||||||
|
// before it reaches mkdir() and DocumentManager::upload_document().
|
||||||
|
$audioDir = str_replace("\\", "/", $audioDir);
|
||||||
|
$cleanSegments = [];
|
||||||
|
foreach (explode("/", $audioDir) as $segment) {
|
||||||
|
if ("" === $segment || "." === $segment || ".." === $segment) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$cleanSegments[] = $segment;
|
||||||
|
}
|
||||||
|
$audioDir = empty($cleanSegments) ? "" : "/".implode("/", $cleanSegments);
|
||||||
|
|
||||||
switch ($type) {
|
switch ($type) {
|
||||||
case 'document':
|
case 'document':
|
||||||
$dirBaseDocuments = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
|
$dirBaseDocuments = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document';
|
||||||
|
|||||||
@@ -39,6 +39,20 @@ $waminame = Database::escape_string($waminame);
|
|||||||
$waminame = api_replace_dangerous_char($waminame);
|
$waminame = api_replace_dangerous_char($waminame);
|
||||||
$waminame = disable_dangerous_file($waminame);
|
$waminame = disable_dangerous_file($waminame);
|
||||||
$wamidir = Security::remove_XSS($wamidir);
|
$wamidir = Security::remove_XSS($wamidir);
|
||||||
|
|
||||||
|
// Neutralize path traversal: wamidir must be a relative path inside the course
|
||||||
|
// document tree, so drop any traversal or absolute-path segments before it
|
||||||
|
// reaches fopen()/DocumentManager::upload_document().
|
||||||
|
$wamidir = str_replace("\\", "/", $wamidir);
|
||||||
|
$cleanSegments = [];
|
||||||
|
foreach (explode("/", $wamidir) as $segment) {
|
||||||
|
if ("" === $segment || "." === $segment || ".." === $segment) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$cleanSegments[] = $segment;
|
||||||
|
}
|
||||||
|
$wamidir = empty($cleanSegments) ? "" : "/".implode("/", $cleanSegments);
|
||||||
|
|
||||||
$content = file_get_contents('php://input');
|
$content = file_get_contents('php://input');
|
||||||
|
|
||||||
if (empty($content)) {
|
if (empty($content)) {
|
||||||
|
|||||||
@@ -136,6 +136,9 @@ switch ($action) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'session_info':
|
case 'session_info':
|
||||||
|
SessionManager::protectSession(null, false);
|
||||||
|
api_protect_limit_for_session_admin();
|
||||||
|
|
||||||
$sessionId = isset($_GET['session_id']) ? $_GET['session_id'] : '';
|
$sessionId = isset($_GET['session_id']) ? $_GET['session_id'] : '';
|
||||||
$sessionInfo = api_get_session_info($sessionId);
|
$sessionInfo = api_get_session_info($sessionId);
|
||||||
|
|
||||||
@@ -166,7 +169,7 @@ switch ($action) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'get_description':
|
case 'get_description':
|
||||||
if (isset($_GET['session'])) {
|
if (CoursesAndSessionsCatalog::userCanView() && isset($_GET['session'])) {
|
||||||
$sessionInfo = api_get_session_info($_GET['session']);
|
$sessionInfo = api_get_session_info($_GET['session']);
|
||||||
echo '<h2>'.$sessionInfo['name'].'</h2>';
|
echo '<h2>'.$sessionInfo['name'].'</h2>';
|
||||||
echo '<div class="home-course-intro"><div class="page-course"><div class="page-course-intro">';
|
echo '<div class="home-course-intro"><div class="page-course"><div class="page-course-intro">';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ $kernel = new AppKernel('', '');
|
|||||||
if (isset($_GET['action'])) {
|
if (isset($_GET['action'])) {
|
||||||
$action = $_GET['action'];
|
$action = $_GET['action'];
|
||||||
|
|
||||||
if ($action == 'time') {
|
if ($action === 'time') {
|
||||||
// Load the Chamilo configuration
|
// Load the Chamilo configuration
|
||||||
$alreadyInstalled = false;
|
$alreadyInstalled = false;
|
||||||
if (file_exists($kernel->getConfigurationFile())) {
|
if (file_exists($kernel->getConfigurationFile())) {
|
||||||
@@ -56,7 +56,7 @@ if (isset($_GET['action'])) {
|
|||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
echo json_encode(['error' => 'Error retrieving data from the current session']);
|
echo json_encode(['error' => 'Error retrieving data from the current session']);
|
||||||
}
|
}
|
||||||
} elseif ($action == 'logout') {
|
} elseif ($action === 'logout') {
|
||||||
require_once __DIR__.'/../../../main/inc/global-min.inc.php';
|
require_once __DIR__.'/../../../main/inc/global-min.inc.php';
|
||||||
|
|
||||||
$userId = api_get_user_id();
|
$userId = api_get_user_id();
|
||||||
|
|||||||
@@ -464,6 +464,8 @@ switch ($action) {
|
|||||||
echo json_encode(['items' => $items]);
|
echo json_encode(['items' => $items]);
|
||||||
break;
|
break;
|
||||||
case 'update_users':
|
case 'update_users':
|
||||||
|
api_protect_admin_script(true);
|
||||||
|
|
||||||
$usersData = json_decode($_POST['users'], true);
|
$usersData = json_decode($_POST['users'], true);
|
||||||
$updatedCount = 0;
|
$updatedCount = 0;
|
||||||
|
|
||||||
@@ -480,11 +482,11 @@ switch ($action) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$updatedData = [
|
$updatedData = [
|
||||||
'firstname' => $userData['firstname'] ?? $currentUserData['firstname'],
|
'firstname' => attr_on_filter(html_filter($userData['firstname'] ?? $currentUserData['firstname'])),
|
||||||
'lastname' => $userData['lastname'] ?? $currentUserData['lastname'],
|
'lastname' => attr_on_filter(html_filter($userData['lastname'] ?? $currentUserData['lastname'])),
|
||||||
'email' => $userData['email'] ?? $currentUserData['email'],
|
'email' => attr_on_filter(html_filter($userData['email'] ?? $currentUserData['email'])),
|
||||||
'phone' => $userData['phone'] ?? $currentUserData['phone'],
|
'phone' => attr_on_filter(html_filter($userData['phone'] ?? $currentUserData['phone'])),
|
||||||
'official_code' => $userData['official_code'] ?? $currentUserData['official_code'],
|
'official_code' => attr_on_filter(html_filter($userData['official_code'] ?? $currentUserData['official_code'])),
|
||||||
'status' => isset($userData['status']) ? (int) $userData['status'] : $currentUserData['status'],
|
'status' => isset($userData['status']) ? (int) $userData['status'] : $currentUserData['status'],
|
||||||
'active' => isset($userData['active']) ? (int) $userData['active'] : $currentUserData['active'],
|
'active' => isset($userData['active']) ? (int) $userData['active'] : $currentUserData['active'],
|
||||||
];
|
];
|
||||||
|
|||||||
+17
-6
@@ -388,6 +388,12 @@ if (isset($this_script) && $this_script == 'sub_language') {
|
|||||||
//getting sub language info
|
//getting sub language info
|
||||||
$sub_language = SubLanguageManager::get_all_information_of_language($_REQUEST['sub_language_id']);
|
$sub_language = SubLanguageManager::get_all_information_of_language($_REQUEST['sub_language_id']);
|
||||||
|
|
||||||
|
// Validate folder names before using them in file paths
|
||||||
|
$parentFolderValid = !empty($parent_language['dokeos_folder']) &&
|
||||||
|
SubLanguageManager::isValidLanguageFolderName($parent_language['dokeos_folder']);
|
||||||
|
$subFolderValid = !empty($sub_language['dokeos_folder']) &&
|
||||||
|
SubLanguageManager::isValidLanguageFolderName($sub_language['dokeos_folder']);
|
||||||
|
|
||||||
$english_language_array = $parent_language_array = $sub_language_array = [];
|
$english_language_array = $parent_language_array = $sub_language_array = [];
|
||||||
|
|
||||||
foreach ($language_files_to_load as $language_file_item) {
|
foreach ($language_files_to_load as $language_file_item) {
|
||||||
@@ -409,10 +415,13 @@ if (isset($this_script) && $this_script == 'sub_language') {
|
|||||||
foreach ($lang_list_result as $item) {
|
foreach ($lang_list_result as $item) {
|
||||||
unset(${$item});
|
unset(${$item});
|
||||||
}
|
}
|
||||||
$parent_file = $langpath.$parent_language['dokeos_folder'].'/'.$language_file_item.'.inc.php';
|
|
||||||
|
|
||||||
if (file_exists($parent_file) && is_file($parent_file)) {
|
if ($parentFolderValid) {
|
||||||
include_once $parent_file;
|
$parent_file = $langpath.$parent_language['dokeos_folder'].'/'.$language_file_item.'.inc.php';
|
||||||
|
|
||||||
|
if (file_exists($parent_file) && is_file($parent_file)) {
|
||||||
|
include_once $parent_file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// parent language array
|
// parent language array
|
||||||
$parent_language_array[$language_file_item] = compact($lang_list_result);
|
$parent_language_array[$language_file_item] = compact($lang_list_result);
|
||||||
@@ -422,9 +431,11 @@ if (isset($this_script) && $this_script == 'sub_language') {
|
|||||||
unset(${$item});
|
unset(${$item});
|
||||||
}
|
}
|
||||||
|
|
||||||
$sub_file = $langpath.$sub_language['dokeos_folder'].'/'.$language_file_item.'.inc.php';
|
if ($subFolderValid) {
|
||||||
if (file_exists($sub_file) && is_file($sub_file)) {
|
$sub_file = $langpath.$sub_language['dokeos_folder'].'/'.$language_file_item.'.inc.php';
|
||||||
include $sub_file;
|
if (file_exists($sub_file) && is_file($sub_file)) {
|
||||||
|
include $sub_file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sub language array
|
// sub language array
|
||||||
|
|||||||
@@ -1,13 +1,43 @@
|
|||||||
<?php
|
<?php
|
||||||
/* For licensing terms, see /license.txt */
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
|
use Chamilo\UserBundle\Entity\User;
|
||||||
use Symfony\Component\Finder\Finder;
|
use Symfony\Component\Finder\Finder;
|
||||||
|
use Twig\Environment;
|
||||||
|
use Twig\Extension\SandboxExtension;
|
||||||
|
use Twig\Loader\ArrayLoader;
|
||||||
|
use Twig\Sandbox\SecurityPolicy;
|
||||||
|
use Twig\TwigFilter;
|
||||||
|
use Twig\TwigFunction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class MailTemplateManager.
|
* Class MailTemplateManager.
|
||||||
*/
|
*/
|
||||||
class MailTemplateManager extends Model
|
class MailTemplateManager extends Model
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Twig tags allowed inside an admin-stored mail template body.
|
||||||
|
*/
|
||||||
|
public const ALLOWED_TAGS = ['if', 'for', 'set', 'apply', 'spaceless', 'autoescape', 'with'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Twig filters allowed inside an admin-stored mail template body. The
|
||||||
|
* callable-accepting gadget filters (filter/map/reduce/sort) are
|
||||||
|
* deliberately excluded to prevent SSTI → RCE.
|
||||||
|
*/
|
||||||
|
public const ALLOWED_FILTERS = [
|
||||||
|
'abs', 'capitalize', 'date', 'date_modify', 'default', 'escape', 'e',
|
||||||
|
'first', 'format', 'join', 'json_encode', 'keys', 'last', 'length',
|
||||||
|
'lower', 'merge', 'nl2br', 'number_format', 'raw', 'replace', 'reverse',
|
||||||
|
'round', 'slice', 'split', 'striptags', 'title', 'trim', 'upper',
|
||||||
|
'url_encode', 'get_lang',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Twig functions allowed inside an admin-stored mail template body.
|
||||||
|
*/
|
||||||
|
public const ALLOWED_FUNCTIONS = ['max', 'min', 'range', 'get_lang'];
|
||||||
|
|
||||||
public $columns = [
|
public $columns = [
|
||||||
'id',
|
'id',
|
||||||
'name',
|
'name',
|
||||||
@@ -113,7 +143,13 @@ class MailTemplateManager extends Model
|
|||||||
);*/
|
);*/
|
||||||
$form->addTextarea(
|
$form->addTextarea(
|
||||||
'email_template',
|
'email_template',
|
||||||
get_lang('Template')
|
get_lang('Template'),
|
||||||
|
['rows' => 20]
|
||||||
|
);
|
||||||
|
|
||||||
|
$form->addLabel(
|
||||||
|
get_lang('Allowed template syntax'),
|
||||||
|
$this->getAllowedSyntaxHelp()
|
||||||
);
|
);
|
||||||
|
|
||||||
$finder = new Finder();
|
$finder = new Finder();
|
||||||
@@ -234,4 +270,87 @@ class MailTemplateManager extends Model
|
|||||||
|
|
||||||
return $result['template'];
|
return $result['template'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds the help block describing the Twig syntax allowed in a mail
|
||||||
|
* template, derived from the same allowlists enforced by
|
||||||
|
* renderSandboxedTemplate() so the form and the sandbox never drift apart.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getAllowedSyntaxHelp(): string
|
||||||
|
{
|
||||||
|
$tags = api_htmlentities(implode(', ', self::ALLOWED_TAGS));
|
||||||
|
$filters = api_htmlentities(implode(', ', self::ALLOWED_FILTERS));
|
||||||
|
$functions = api_htmlentities(implode(', ', self::ALLOWED_FUNCTIONS));
|
||||||
|
|
||||||
|
$html = '<div class="space-y-2 mt-2 rounded-lg border border-gray-25 bg-gray-15 p-4 text-sm text-gray-70">';
|
||||||
|
$html .= '<p><strong>'.api_htmlentities(get_lang('Tags')).':</strong> <code>'.$tags.'</code></p>';
|
||||||
|
$html .= '<p><strong>'.api_htmlentities(get_lang('Filters')).':</strong> <code>'.$filters.'</code></p>';
|
||||||
|
$html .= '<p><strong>'.api_htmlentities(get_lang('Functions')).':</strong> <code>'.$functions.'</code></p>';
|
||||||
|
$html .= '<p>'.api_htmlentities(get_lang('Available variables depend on the template type, for example {{ user.getUsername() }} or {{ user.getEmail() }}.')).'</p>';
|
||||||
|
$html .= '<p>'.api_htmlentities(get_lang('For security reasons, any other Twig function, filter (such as filter, map, reduce or sort) or PHP call is blocked.')).'</p>';
|
||||||
|
$html .= '</div>';
|
||||||
|
|
||||||
|
return $html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders an admin-stored mail template body through a sandboxed Twig
|
||||||
|
* environment.
|
||||||
|
*
|
||||||
|
* Stored mail templates are untrusted content (any platform admin can edit
|
||||||
|
* them) and must never be compiled with the full application Twig: the
|
||||||
|
* non-sandboxed environment exposes the callable-accepting filters
|
||||||
|
* (filter/map/reduce/sort) that turn a template body into a Server-Side
|
||||||
|
* Template Injection → Remote Code Execution gadget.
|
||||||
|
*
|
||||||
|
* The sandbox here uses an explicit allow-list of tags, filters, functions
|
||||||
|
* and entity getters; everything else — including the RCE gadget filters —
|
||||||
|
* is rejected. When rendering is refused or fails, an empty string is
|
||||||
|
* returned so the caller falls back to the default file-based template.
|
||||||
|
*
|
||||||
|
* @param string $templateText The admin-stored Twig template body
|
||||||
|
* @param array $params The render context (template variables)
|
||||||
|
*
|
||||||
|
* @return string The rendered body, or '' when rendering is rejected
|
||||||
|
*/
|
||||||
|
public static function renderSandboxedTemplate(string $templateText, array $params): string
|
||||||
|
{
|
||||||
|
if ('' === trim($templateText)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedMethods = [
|
||||||
|
User::class => [
|
||||||
|
'getId', 'getUsername', 'getFirstname', 'getLastname',
|
||||||
|
'getEmail', 'getStatus', 'getOfficialCode', 'getPhone',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$allowedProperties = [];
|
||||||
|
|
||||||
|
$policy = new SecurityPolicy(
|
||||||
|
self::ALLOWED_TAGS,
|
||||||
|
self::ALLOWED_FILTERS,
|
||||||
|
$allowedMethods,
|
||||||
|
$allowedProperties,
|
||||||
|
self::ALLOWED_FUNCTIONS
|
||||||
|
);
|
||||||
|
|
||||||
|
$twig = new Environment(
|
||||||
|
new ArrayLoader(['mail_template' => $templateText]),
|
||||||
|
['autoescape' => 'html', 'cache' => false, 'strict_variables' => false]
|
||||||
|
);
|
||||||
|
$twig->addExtension(new SandboxExtension($policy, true));
|
||||||
|
$twig->addFilter(new TwigFilter('get_lang', 'get_lang'));
|
||||||
|
$twig->addFunction(new TwigFunction('get_lang', 'get_lang'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
return $twig->render('mail_template', $params);
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
error_log('Refused to render stored mail template in sandbox: '.$e->getMessage());
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,30 @@
|
|||||||
*/
|
*/
|
||||||
class MoodleImport
|
class MoodleImport
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Loads XML into a DOMDocument hardened against XXE attacks.
|
||||||
|
*
|
||||||
|
* External entity loading is blocked (libxml_disable_entity_loader) so a
|
||||||
|
* malicious Moodle backup cannot trigger SSRF via network entities nor
|
||||||
|
* disclose local files via file:// entities. LIBXML_NONET is passed as
|
||||||
|
* defense-in-depth. The previous loader state is restored afterwards.
|
||||||
|
*
|
||||||
|
* @param \DOMDocument $doc document populated in place
|
||||||
|
* @param string $xml XML to parse
|
||||||
|
*
|
||||||
|
* @return bool result of DOMDocument::loadXML()
|
||||||
|
*/
|
||||||
|
private static function loadXml(\DOMDocument $doc, $xml)
|
||||||
|
{
|
||||||
|
$previous = @libxml_disable_entity_loader(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
return @$doc->loadXML($xml, LIBXML_NONET);
|
||||||
|
} finally {
|
||||||
|
@libxml_disable_entity_loader($previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import moodle file.
|
* Import moodle file.
|
||||||
*
|
*
|
||||||
@@ -134,7 +158,7 @@ class MoodleImport
|
|||||||
|
|
||||||
$xml = @file_get_contents($destinationDir.'/moodle_backup.xml');
|
$xml = @file_get_contents($destinationDir.'/moodle_backup.xml');
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$res = @$doc->loadXML($xml);
|
$res = self::loadXml($doc, $xml);
|
||||||
|
|
||||||
if (empty($res)) {
|
if (empty($res)) {
|
||||||
removeDir($destinationDir);
|
removeDir($destinationDir);
|
||||||
@@ -617,7 +641,7 @@ class MoodleImport
|
|||||||
$xml = file_get_contents($moodleBackupXmlPath);
|
$xml = file_get_contents($moodleBackupXmlPath);
|
||||||
|
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$doc->loadXML($xml);
|
self::loadXml($doc, $xml);
|
||||||
$backupRelease = $doc->getElementsByTagName('backup_release');
|
$backupRelease = $doc->getElementsByTagName('backup_release');
|
||||||
$version = null;
|
$version = null;
|
||||||
foreach ($backupRelease as $release) {
|
foreach ($backupRelease as $release) {
|
||||||
@@ -788,7 +812,7 @@ class MoodleImport
|
|||||||
public function readSections($xml, $destinationDir)
|
public function readSections($xml, $destinationDir)
|
||||||
{
|
{
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$res = @$doc->loadXML($xml);
|
$res = self::loadXml($doc, $xml);
|
||||||
if (empty($res)) {
|
if (empty($res)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -823,7 +847,7 @@ class MoodleImport
|
|||||||
public function readSectionModule($sectionInfoXml)
|
public function readSectionModule($sectionInfoXml)
|
||||||
{
|
{
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$res = @$doc->loadXML($sectionInfoXml);
|
$res = self::loadXml($doc, $sectionInfoXml);
|
||||||
if (empty($res)) {
|
if (empty($res)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -852,7 +876,7 @@ class MoodleImport
|
|||||||
public function readLessonModule($moduleXml)
|
public function readLessonModule($moduleXml)
|
||||||
{
|
{
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$res = @$doc->loadXML($moduleXml);
|
$res = self::loadXml($doc, $moduleXml);
|
||||||
if (empty($res)) {
|
if (empty($res)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -906,7 +930,7 @@ class MoodleImport
|
|||||||
public function readAssignModule($moduleXml)
|
public function readAssignModule($moduleXml)
|
||||||
{
|
{
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$res = @$doc->loadXML($moduleXml);
|
$res = self::loadXml($doc, $moduleXml);
|
||||||
if (empty($res)) {
|
if (empty($res)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -991,7 +1015,7 @@ class MoodleImport
|
|||||||
public function readScormModule($moduleXml)
|
public function readScormModule($moduleXml)
|
||||||
{
|
{
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$res = @$doc->loadXML($moduleXml);
|
$res = self::loadXml($doc, $moduleXml);
|
||||||
if (empty($res)) {
|
if (empty($res)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1025,7 +1049,7 @@ class MoodleImport
|
|||||||
public function readGlossaryModule($moduleXml, $moduleId)
|
public function readGlossaryModule($moduleXml, $moduleId)
|
||||||
{
|
{
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$res = @$doc->loadXML($moduleXml);
|
$res = self::loadXml($doc, $moduleXml);
|
||||||
if (empty($res)) {
|
if (empty($res)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1059,7 +1083,7 @@ class MoodleImport
|
|||||||
public function readHtmlModule($moduleXml, $moduleName)
|
public function readHtmlModule($moduleXml, $moduleName)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($moduleXml);
|
$moduleRes = self::loadXml($moduleDoc, $moduleXml);
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1660,7 +1684,7 @@ class MoodleImport
|
|||||||
public function readForumModule($moduleXml)
|
public function readForumModule($moduleXml)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($moduleXml);
|
$moduleRes = self::loadXml($moduleDoc, $moduleXml);
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1715,7 +1739,7 @@ class MoodleImport
|
|||||||
public function readFolderModule($moduleXml)
|
public function readFolderModule($moduleXml)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($moduleXml);
|
$moduleRes = self::loadXml($moduleDoc, $moduleXml);
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1748,7 +1772,7 @@ class MoodleImport
|
|||||||
public function readResourceModule($moduleXml)
|
public function readResourceModule($moduleXml)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($moduleXml);
|
$moduleRes = self::loadXml($moduleDoc, $moduleXml);
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1781,7 +1805,7 @@ class MoodleImport
|
|||||||
public function readUrlModule($moduleXml)
|
public function readUrlModule($moduleXml)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($moduleXml);
|
$moduleRes = self::loadXml($moduleDoc, $moduleXml);
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1811,7 +1835,7 @@ class MoodleImport
|
|||||||
public function readQuizGradeModule($gradeXml, $quizId)
|
public function readQuizGradeModule($gradeXml, $quizId)
|
||||||
{
|
{
|
||||||
$doc = new DOMDocument();
|
$doc = new DOMDocument();
|
||||||
$gradeRes = @$doc->loadXML($gradeXml);
|
$gradeRes = self::loadXml($doc, $gradeXml);
|
||||||
if (empty($gradeRes)) {
|
if (empty($gradeRes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1851,7 +1875,7 @@ class MoodleImport
|
|||||||
public function readQuizModule($moduleXml)
|
public function readQuizModule($moduleXml)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($moduleXml);
|
$moduleRes = self::loadXml($moduleDoc, $moduleXml);
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1895,7 +1919,7 @@ class MoodleImport
|
|||||||
public function readQuizModuleV4($moduleXml)
|
public function readQuizModuleV4($moduleXml)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
@$moduleDoc->loadXML($moduleXml);
|
self::loadXml($moduleDoc, $moduleXml);
|
||||||
|
|
||||||
$quizData = [];
|
$quizData = [];
|
||||||
$quizNodes = $moduleDoc->getElementsByTagName('quiz');
|
$quizNodes = $moduleDoc->getElementsByTagName('quiz');
|
||||||
@@ -1932,10 +1956,10 @@ class MoodleImport
|
|||||||
public function readMainQuestionsXmlV4($questionsXml, $quizXml)
|
public function readMainQuestionsXmlV4($questionsXml, $quizXml)
|
||||||
{
|
{
|
||||||
$questionsDoc = new DOMDocument();
|
$questionsDoc = new DOMDocument();
|
||||||
@$questionsDoc->loadXML($questionsXml);
|
self::loadXml($questionsDoc, $questionsXml);
|
||||||
|
|
||||||
$quizDoc = new DOMDocument();
|
$quizDoc = new DOMDocument();
|
||||||
@$quizDoc->loadXML($quizXml);
|
self::loadXml($quizDoc, $quizXml);
|
||||||
|
|
||||||
$isByCategory = $quizDoc->getElementsByTagName('question_set_reference')->length > 0;
|
$isByCategory = $quizDoc->getElementsByTagName('question_set_reference')->length > 0;
|
||||||
$isByBankReference = $quizDoc->getElementsByTagName('question_reference')->length > 0;
|
$isByBankReference = $quizDoc->getElementsByTagName('question_reference')->length > 0;
|
||||||
@@ -1960,7 +1984,7 @@ class MoodleImport
|
|||||||
public function readFolderModuleFilesXml($filesXml, $contextId = null)
|
public function readFolderModuleFilesXml($filesXml, $contextId = null)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($filesXml);
|
$moduleRes = self::loadXml($moduleDoc, $filesXml);
|
||||||
|
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2005,7 +2029,7 @@ class MoodleImport
|
|||||||
public function readMainFilesXml($filesXml, $contextId)
|
public function readMainFilesXml($filesXml, $contextId)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($filesXml);
|
$moduleRes = self::loadXml($moduleDoc, $filesXml);
|
||||||
|
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -2048,7 +2072,7 @@ class MoodleImport
|
|||||||
public function readMainQuestionsXml($questionsXml, $questionId)
|
public function readMainQuestionsXml($questionsXml, $questionId)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($questionsXml);
|
$moduleRes = self::loadXml($moduleDoc, $questionsXml);
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2610,7 +2634,7 @@ class MoodleImport
|
|||||||
public function getAllQuestionFiles($filesXml)
|
public function getAllQuestionFiles($filesXml)
|
||||||
{
|
{
|
||||||
$moduleDoc = new DOMDocument();
|
$moduleDoc = new DOMDocument();
|
||||||
$moduleRes = @$moduleDoc->loadXML($filesXml);
|
$moduleRes = self::loadXml($moduleDoc, $filesXml);
|
||||||
|
|
||||||
if (empty($moduleRes)) {
|
if (empty($moduleRes)) {
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -10355,12 +10355,6 @@ function api_set_noreply_and_from_address_to_mailer(PHPMailer $mailer, array $se
|
|||||||
$senderName = !empty($sender['name']) ? $sender['name'] : $notification->getDefaultPlatformSenderName();
|
$senderName = !empty($sender['name']) ? $sender['name'] : $notification->getDefaultPlatformSenderName();
|
||||||
$senderEmail = !empty($sender['email']) ? $sender['email'] : $notification->getDefaultPlatformSenderEmail();
|
$senderEmail = !empty($sender['email']) ? $sender['email'] : $notification->getDefaultPlatformSenderEmail();
|
||||||
|
|
||||||
// Send errors to the platform admin
|
|
||||||
$adminEmail = api_get_setting('emailAdministrator');
|
|
||||||
if (PHPMailer::ValidateAddress($adminEmail)) {
|
|
||||||
$mailer->AddCustomHeader('Errors-To: '.$adminEmail);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reply to first
|
// Reply to first
|
||||||
if (!$avoidReplyToAddress) {
|
if (!$avoidReplyToAddress) {
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class ChamiloSession extends System\Session
|
|||||||
*/
|
*/
|
||||||
public static function session_lifetime()
|
public static function session_lifetime()
|
||||||
{
|
{
|
||||||
return api_get_configuration_value('session_lifetime');
|
return (int) api_get_configuration_value('session_lifetime');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2489,15 +2489,17 @@ class GroupManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$hasCheckbox = api_is_allowed_to_edit(false, true) && count($group_list) > 1;
|
||||||
|
$defaultSortColumn = $hasCheckbox ? 1 : 0;
|
||||||
$table = new SortableTableFromArrayConfig(
|
$table = new SortableTableFromArrayConfig(
|
||||||
$group_data,
|
$group_data,
|
||||||
1,
|
$defaultSortColumn,
|
||||||
20,
|
20,
|
||||||
'group_category_'.$category_id
|
'group_category_'.$category_id
|
||||||
);
|
);
|
||||||
$table->set_additional_parameters(['category' => $category_id]);
|
$table->set_additional_parameters(['category' => $category_id]);
|
||||||
$column = 0;
|
$column = 0;
|
||||||
if (api_is_allowed_to_edit(false, true) && count($group_list) > 1) {
|
if ($hasCheckbox) {
|
||||||
$table->set_header($column++, '', false);
|
$table->set_header($column++, '', false);
|
||||||
}
|
}
|
||||||
$table->set_header($column++, get_lang('Groups'));
|
$table->set_header($column++, get_lang('Groups'));
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
/* For licensing terms, see /license.txt */
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Http\SafeHttp;
|
||||||
use Chamilo\CourseBundle\Entity\CLink;
|
use Chamilo\CourseBundle\Entity\CLink;
|
||||||
use GuzzleHttp\Client;
|
use GuzzleHttp\Client;
|
||||||
|
|
||||||
@@ -1816,6 +1817,21 @@ class Link extends Model
|
|||||||
|
|
||||||
public static function checkUrl(string $url): bool
|
public static function checkUrl(string $url): bool
|
||||||
{
|
{
|
||||||
|
// SSRF guard (CWE-918): reject targets that resolve to a
|
||||||
|
// loopback/private/reserved/link-local address (incl. the cloud
|
||||||
|
// metadata endpoint) or use a non-http(s) scheme before any request.
|
||||||
|
if (null === SafeHttp::resolveSafeIp($url)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-validate every redirect hop so a 30x cannot bounce the request to
|
||||||
|
// an internal host after the initial check passed.
|
||||||
|
$onRedirect = function ($request, $response, $uri) {
|
||||||
|
if (null === SafeHttp::resolveSafeIp((string) $uri)) {
|
||||||
|
throw new \RuntimeException("Blocked redirect to a non-public host.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$defaults = [
|
$defaults = [
|
||||||
'allow_redirects' => [
|
'allow_redirects' => [
|
||||||
'max' => 5, // max number of redirects allowed
|
'max' => 5, // max number of redirects allowed
|
||||||
@@ -1823,6 +1839,7 @@ class Link extends Model
|
|||||||
'referer' => true, // whether to add the Referer header when redirecting
|
'referer' => true, // whether to add the Referer header when redirecting
|
||||||
'protocols' => ['http', 'https'], // protocols allowed to be redirected to
|
'protocols' => ['http', 'https'], // protocols allowed to be redirected to
|
||||||
'track_redirects' => true, // whether to keep track of the number of redirects
|
'track_redirects' => true, // whether to keep track of the number of redirects
|
||||||
|
'on_redirect' => $onRedirect,
|
||||||
],
|
],
|
||||||
'connect_timeout' => 4,
|
'connect_timeout' => 4,
|
||||||
'timeout' => 4,
|
'timeout' => 4,
|
||||||
|
|||||||
@@ -2906,11 +2906,11 @@ class MessageManager
|
|||||||
if (api_get_configuration_value('mail_template_system') == true) {
|
if (api_get_configuration_value('mail_template_system') == true) {
|
||||||
$mailTemplateManager = new MailTemplateManager();
|
$mailTemplateManager = new MailTemplateManager();
|
||||||
$templateText = $mailTemplateManager->getTemplateByType('new_user_mail_to_admin_approval.tpl');
|
$templateText = $mailTemplateManager->getTemplateByType('new_user_mail_to_admin_approval.tpl');
|
||||||
if (empty($templateText)) {
|
if (!empty($templateText)) {
|
||||||
} else {
|
// Stored mail templates are admin-edited and therefore untrusted: render
|
||||||
// custom procedure to load a template as a string (doesn't use cache so may slow down)
|
// them through a sandboxed Twig environment instead of compiling the raw
|
||||||
$template = $tplMailBody->twig->createTemplate($templateText);
|
// string with the full application Twig (which would allow SSTI → RCE).
|
||||||
$emailbody = $template->render($tplMailBody->params);
|
$emailbody = MailTemplateManager::renderSandboxedTemplate($templateText, $tplMailBody->params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (empty($emailbody)) {
|
if (empty($emailbody)) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -44,46 +44,63 @@ class ResourceExport extends ActivityExport
|
|||||||
*/
|
*/
|
||||||
public function getData(int $resourceId, int $sectionId, ?int $moduleId = null): array
|
public function getData(int $resourceId, int $sectionId, ?int $moduleId = null): array
|
||||||
{
|
{
|
||||||
if (empty($this->course->resources[RESOURCE_DOCUMENT][$resourceId])) {
|
$docBucket = $this->course->resources[\defined('RESOURCE_DOCUMENT') ? RESOURCE_DOCUMENT : 'document']
|
||||||
return [];
|
?? $this->course->resources['document']
|
||||||
|
?? [];
|
||||||
|
|
||||||
|
$resource = $docBucket[$resourceId] ?? null;
|
||||||
|
|
||||||
|
if (null === $resource) {
|
||||||
|
return [
|
||||||
|
'id' => $resourceId,
|
||||||
|
'moduleid' => $resourceId,
|
||||||
|
'modulename' => 'resource',
|
||||||
|
'contextid' => 0,
|
||||||
|
'name' => 'Resource '.$resourceId,
|
||||||
|
'intro' => '',
|
||||||
|
'display' => 0,
|
||||||
|
'sectionid' => $sectionId,
|
||||||
|
'sectionnumber' => 1,
|
||||||
|
'timemodified' => time(),
|
||||||
|
'users' => [],
|
||||||
|
'files' => [],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$resource = $this->course->resources[RESOURCE_DOCUMENT][$resourceId];
|
$documentPath = (string) ($resource->path ?? '');
|
||||||
|
$effectiveModuleId = (int) ($moduleId ?? ($resource->source_id ?? 0));
|
||||||
|
|
||||||
$name = (string) ($resource->title ?? '');
|
|
||||||
if ($sectionId > 0) {
|
|
||||||
$name = $this->lpItemTitle($sectionId, RESOURCE_DOCUMENT, $resourceId, $name);
|
|
||||||
}
|
|
||||||
$name = $this->sanitizeMoodleActivityName($name, 255);
|
|
||||||
|
|
||||||
$effectiveModuleId = (int) ($moduleId ?? $resource->source_id);
|
|
||||||
if ($effectiveModuleId <= 0) {
|
if ($effectiveModuleId <= 0) {
|
||||||
$effectiveModuleId = (int) $resource->source_id;
|
$effectiveModuleId = (int) ($resource->source_id ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$name = (string) ($resource->title ?? ('Resource '.$resourceId));
|
||||||
|
|
||||||
|
if ($sectionId > 0) {
|
||||||
|
$name = $this->lpItemTitle(
|
||||||
|
$sectionId,
|
||||||
|
\defined('RESOURCE_DOCUMENT') ? (string) RESOURCE_DOCUMENT : 'document',
|
||||||
|
$resourceId,
|
||||||
|
$name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->sanitizeMoodleActivityName($name, 255);
|
||||||
$resourceFile = $this->buildResourceFileEntry($resource, $effectiveModuleId);
|
$resourceFile = $this->buildResourceFileEntry($resource, $effectiveModuleId);
|
||||||
|
|
||||||
$introResult = $this->extractEmbeddedFilesAndNormalizeContent(
|
|
||||||
(string) ($resource->comment ?? ''),
|
|
||||||
$effectiveModuleId,
|
|
||||||
'mod_resource',
|
|
||||||
'intro',
|
|
||||||
0,
|
|
||||||
fn (int $sequence): int => $this->buildResourceIntroFileId($effectiveModuleId, $sequence)
|
|
||||||
);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $resourceId,
|
'id' => (int) ($resource->source_id ?? $resourceId),
|
||||||
'moduleid' => $effectiveModuleId,
|
'moduleid' => $effectiveModuleId,
|
||||||
'modulename' => 'resource',
|
'modulename' => 'resource',
|
||||||
'contextid' => $effectiveModuleId,
|
'contextid' => $effectiveModuleId,
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'intro' => $introResult['content'],
|
'intro' => (string) ($resource->comment ?? ''),
|
||||||
|
'display' => $this->resolveResourceDisplay($documentPath),
|
||||||
'sectionid' => $sectionId,
|
'sectionid' => $sectionId,
|
||||||
'sectionnumber' => 1,
|
'sectionnumber' => 1,
|
||||||
'timemodified' => time(),
|
'timemodified' => time(),
|
||||||
'users' => [],
|
'users' => [],
|
||||||
'files' => array_merge([$resourceFile], $introResult['files']),
|
'files' => [$resourceFile],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,19 +239,24 @@ class ResourceExport extends ActivityExport
|
|||||||
$xmlContent .= '<activity id="'.$resourceData['id'].'" moduleid="'.$resourceData['moduleid'].'" modulename="resource" contextid="'.$resourceData['contextid'].'">'.PHP_EOL;
|
$xmlContent .= '<activity id="'.$resourceData['id'].'" moduleid="'.$resourceData['moduleid'].'" modulename="resource" contextid="'.$resourceData['contextid'].'">'.PHP_EOL;
|
||||||
$xmlContent .= ' <resource id="'.$resourceData['id'].'">'.PHP_EOL;
|
$xmlContent .= ' <resource id="'.$resourceData['id'].'">'.PHP_EOL;
|
||||||
$xmlContent .= ' <name>'.htmlspecialchars((string) $resourceData['name']).'</name>'.PHP_EOL;
|
$xmlContent .= ' <name>'.htmlspecialchars((string) $resourceData['name']).'</name>'.PHP_EOL;
|
||||||
$xmlContent .= ' <intro><![CDATA['.(string) $resourceData['intro'].']]></intro>'.PHP_EOL;
|
$xmlContent .= ' <intro>'.htmlspecialchars((string) $resourceData['intro']).'</intro>'.PHP_EOL;
|
||||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||||
$xmlContent .= ' <tobemigrated>0</tobemigrated>'.PHP_EOL;
|
$xmlContent .= ' <tobemigrated>0</tobemigrated>'.PHP_EOL;
|
||||||
$xmlContent .= ' <legacyfiles>0</legacyfiles>'.PHP_EOL;
|
$xmlContent .= ' <legacyfiles>0</legacyfiles>'.PHP_EOL;
|
||||||
$xmlContent .= ' <legacyfileslast>$@NULL@$</legacyfileslast>'.PHP_EOL;
|
$xmlContent .= ' <legacyfileslast>$@NULL@$</legacyfileslast>'.PHP_EOL;
|
||||||
$xmlContent .= ' <display>0</display>'.PHP_EOL;
|
$xmlContent .= ' <display>'.(int) ($resourceData['display'] ?? 0).'</display>'.PHP_EOL;
|
||||||
$xmlContent .= ' <displayoptions>a:1:{s:10:"printintro";i:1;}</displayoptions>'.PHP_EOL;
|
$xmlContent .= ' <displayoptions>a:1:{s:10:"printintro";i:1;}</displayoptions>'.PHP_EOL;
|
||||||
$xmlContent .= ' <filterfiles>0</filterfiles>'.PHP_EOL;
|
$xmlContent .= ' <filterfiles>0</filterfiles>'.PHP_EOL;
|
||||||
$xmlContent .= ' <revision>1</revision>'.PHP_EOL;
|
$xmlContent .= ' <revision>1</revision>'.PHP_EOL;
|
||||||
$xmlContent .= ' <timemodified>'.$resourceData['timemodified'].'</timemodified>'.PHP_EOL;
|
$xmlContent .= ' <timemodified>'.(int) $resourceData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||||
$xmlContent .= ' </resource>'.PHP_EOL;
|
$xmlContent .= ' </resource>'.PHP_EOL;
|
||||||
$xmlContent .= '</activity>'.PHP_EOL;
|
$xmlContent .= '</activity>'.PHP_EOL;
|
||||||
|
|
||||||
$this->createXmlFile('resource', $xmlContent, $resourceDir);
|
$this->createXmlFile('resource', $xmlContent, $resourceDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function resolveResourceDisplay(string $documentPath): int
|
||||||
|
{
|
||||||
|
return 'application/pdf' === $this->guessMimeType($documentPath) ? 1 : 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,48 +47,48 @@ class UrlExport extends ActivityExport
|
|||||||
/**
|
/**
|
||||||
* Get URL data for the course.
|
* Get URL data for the course.
|
||||||
*/
|
*/
|
||||||
public function getData(int $activityId, int $sectionId, ?int $moduleId = null): ?array
|
public function getData(int $activityId, int $sectionId): ?array
|
||||||
{
|
{
|
||||||
if (empty($this->course->resources['link'][$activityId])) {
|
$url = $this->course->resources['link'][$activityId] ?? null;
|
||||||
return null;
|
|
||||||
|
if (null === $url) {
|
||||||
|
return [
|
||||||
|
'id' => $activityId,
|
||||||
|
'moduleid' => $activityId,
|
||||||
|
'modulename' => 'url',
|
||||||
|
'contextid' => (int) ($this->course->info['real_id'] ?? 0),
|
||||||
|
'name' => 'URL '.$activityId,
|
||||||
|
'description' => '',
|
||||||
|
'externalurl' => '',
|
||||||
|
'display' => 6,
|
||||||
|
'displayoptions' => $this->buildUrlDisplayOptions(),
|
||||||
|
'timecreated' => time(),
|
||||||
|
'timemodified' => time(),
|
||||||
|
'sectionid' => $sectionId,
|
||||||
|
'sectionnumber' => 0,
|
||||||
|
'users' => [],
|
||||||
|
'files' => [],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$url = $this->course->resources['link'][$activityId];
|
$src = isset($url->obj) ? $url->obj : $url;
|
||||||
|
|
||||||
$effectiveModuleId = (int) ($moduleId ?? $activityId);
|
|
||||||
if ($effectiveModuleId <= 0) {
|
|
||||||
$effectiveModuleId = $activityId;
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = (string) ($url->title ?? '');
|
|
||||||
if ($sectionId > 0) {
|
|
||||||
$name = $this->lpItemTitle($sectionId, RESOURCE_LINK, $activityId, $name);
|
|
||||||
}
|
|
||||||
$name = $this->sanitizeMoodleActivityName($name, 255);
|
|
||||||
|
|
||||||
$descriptionResult = $this->extractEmbeddedFilesAndNormalizeContent(
|
|
||||||
(string) ($url->description ?? ''),
|
|
||||||
$effectiveModuleId,
|
|
||||||
'mod_url',
|
|
||||||
'intro',
|
|
||||||
0,
|
|
||||||
fn (int $sequence): int => $this->buildUrlEmbeddedFileId($effectiveModuleId, $sequence)
|
|
||||||
);
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'id' => $activityId,
|
'id' => (int) $activityId,
|
||||||
'moduleid' => $effectiveModuleId,
|
'moduleid' => (int) $activityId,
|
||||||
'modulename' => 'url',
|
'modulename' => 'url',
|
||||||
'contextid' => $effectiveModuleId,
|
'contextid' => (int) $this->course->info['real_id'],
|
||||||
'name' => $name,
|
'name' => (string) ($src->title ?: $src->url),
|
||||||
'description' => $descriptionResult['content'],
|
'description' => (string) ($src->description ?? ''),
|
||||||
'externalurl' => (string) ($url->url ?? ''),
|
'externalurl' => (string) $src->url,
|
||||||
|
'display' => 6,
|
||||||
|
'displayoptions' => $this->buildUrlDisplayOptions(),
|
||||||
'timecreated' => time(),
|
'timecreated' => time(),
|
||||||
'timemodified' => time(),
|
'timemodified' => time(),
|
||||||
'sectionid' => $sectionId,
|
'sectionid' => $sectionId,
|
||||||
'sectionnumber' => 0,
|
'sectionnumber' => 0,
|
||||||
'users' => [],
|
'users' => [],
|
||||||
'files' => $descriptionResult['files'],
|
'files' => [],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,21 +97,21 @@ class UrlExport extends ActivityExport
|
|||||||
*/
|
*/
|
||||||
private function createUrlXml(array $urlData, string $urlDir): void
|
private function createUrlXml(array $urlData, string $urlDir): void
|
||||||
{
|
{
|
||||||
$xmlContent = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
$xml = '<?xml version="1.0" encoding="UTF-8"?>'.PHP_EOL;
|
||||||
$xmlContent .= '<activity id="'.$urlData['id'].'" moduleid="'.$urlData['moduleid'].'" modulename="'.$urlData['modulename'].'" contextid="'.$urlData['contextid'].'">'.PHP_EOL;
|
$xml .= '<activity id="'.$urlData['id'].'" moduleid="'.$urlData['moduleid'].'" modulename="url" contextid="'.$urlData['contextid'].'">'.PHP_EOL;
|
||||||
$xmlContent .= ' <url id="'.$urlData['id'].'">'.PHP_EOL;
|
$xml .= ' <url id="'.$urlData['id'].'">'.PHP_EOL;
|
||||||
$xmlContent .= ' <name>'.htmlspecialchars((string) $urlData['name']).'</name>'.PHP_EOL;
|
$xml .= ' <name>'.htmlspecialchars((string) $urlData['name']).'</name>'.PHP_EOL;
|
||||||
$xmlContent .= ' <intro><![CDATA['.(string) $urlData['description'].']]></intro>'.PHP_EOL;
|
$xml .= ' <intro></intro>'.PHP_EOL;
|
||||||
$xmlContent .= ' <introformat>1</introformat>'.PHP_EOL;
|
$xml .= ' <introformat>1</introformat>'.PHP_EOL;
|
||||||
$xmlContent .= ' <externalurl>'.htmlspecialchars((string) $urlData['externalurl']).'</externalurl>'.PHP_EOL;
|
$xml .= ' <externalurl>'.htmlspecialchars((string) $urlData['externalurl']).'</externalurl>'.PHP_EOL;
|
||||||
$xmlContent .= ' <display>0</display>'.PHP_EOL;
|
$xml .= ' <display>'.(int) ($urlData['display'] ?? 6).'</display>'.PHP_EOL;
|
||||||
$xmlContent .= ' <displayoptions>a:1:{s:10:"printintro";i:1;}</displayoptions>'.PHP_EOL;
|
$xml .= ' <displayoptions>'.htmlspecialchars((string) ($urlData['displayoptions'] ?? 'a:0:{}')).'</displayoptions>'.PHP_EOL;
|
||||||
$xmlContent .= ' <parameters>a:0:{}</parameters>'.PHP_EOL;
|
$xml .= ' <parameters>a:0:{}</parameters>'.PHP_EOL;
|
||||||
$xmlContent .= ' <timemodified>'.$urlData['timemodified'].'</timemodified>'.PHP_EOL;
|
$xml .= ' <timemodified>'.(int) $urlData['timemodified'].'</timemodified>'.PHP_EOL;
|
||||||
$xmlContent .= ' </url>'.PHP_EOL;
|
$xml .= ' </url>'.PHP_EOL;
|
||||||
$xmlContent .= '</activity>';
|
$xml .= '</activity>'.PHP_EOL;
|
||||||
|
|
||||||
$this->createXmlFile('url', $xmlContent, $urlDir);
|
$this->createXmlFile('url', $xml, $urlDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,4 +121,12 @@ class UrlExport extends ActivityExport
|
|||||||
{
|
{
|
||||||
return 1200000000 + max(0, $moduleId) + max(1, $sequence);
|
return 1200000000 + max(0, $moduleId) + max(1, $sequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function buildUrlDisplayOptions(): string
|
||||||
|
{
|
||||||
|
return serialize([
|
||||||
|
'popupwidth' => 1024,
|
||||||
|
'popupheight' => 768,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
/* See license terms in /license.txt */
|
/* See license terms in /license.txt */
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Mpdf\SafeMpdfHttpClient;
|
||||||
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
|
use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
|
||||||
use Mpdf\Mpdf;
|
use Mpdf\Mpdf;
|
||||||
use Mpdf\MpdfException;
|
use Mpdf\MpdfException;
|
||||||
@@ -78,7 +79,11 @@ class PDF
|
|||||||
'margin_footer' => 8,
|
'margin_footer' => 8,
|
||||||
'orientation' => $orientation,
|
'orientation' => $orientation,
|
||||||
'tempDir' => api_get_path(SYS_ARCHIVE_PATH).'mpdf/',
|
'tempDir' => api_get_path(SYS_ARCHIVE_PATH).'mpdf/',
|
||||||
]
|
],
|
||||||
|
// Inject an SSRF-safe HTTP client so mPDF cannot fetch remote
|
||||||
|
// `<img src>`/CSS `url()` pointing to internal/private hosts or the
|
||||||
|
// cloud metadata endpoint when rendering user-supplied HTML.
|
||||||
|
SafeMpdfHttpClient::container()
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->pdf->margin_footer = $params['margin_footer'];
|
$this->pdf->margin_footer = $params['margin_footer'];
|
||||||
|
|||||||
@@ -256,11 +256,25 @@ class HTML_QuickForm_Renderer_ArraySmarty extends HTML_QuickForm_Renderer_Array
|
|||||||
unset($elAry['keys']);
|
unset($elAry['keys']);
|
||||||
// where should we put this element...
|
// where should we put this element...
|
||||||
if (is_array($this->_currentGroup) && ('group' != $elAry['type'])) {
|
if (is_array($this->_currentGroup) && ('group' != $elAry['type'])) {
|
||||||
$toEval = '$this->_currentGroup' . $sKeys . ' = $elAry;';
|
$ref = &$this->_currentGroup;
|
||||||
} else {
|
} else {
|
||||||
$toEval = '$this->_ary' . $sKeys . ' = $elAry;';
|
$ref = &$this->_ary;
|
||||||
}
|
}
|
||||||
eval($toEval);
|
preg_match_all('/\[(?:\'((?:[^\'\\\\]|\\\\.)*)\')?\]/', $sKeys, $matches, PREG_SET_ORDER);
|
||||||
|
foreach ($matches as $match) {
|
||||||
|
if ('[]' === $match[0]) {
|
||||||
|
$ref[] = [];
|
||||||
|
end($ref);
|
||||||
|
$ref = &$ref[key($ref)];
|
||||||
|
} else {
|
||||||
|
$key = str_replace(['\\\\', "\\'"], ['\\', "'"], $match[1]);
|
||||||
|
if (!isset($ref[$key]) || !is_array($ref[$key])) {
|
||||||
|
$ref[$key] = [];
|
||||||
|
}
|
||||||
|
$ref = &$ref[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$ref = $elAry;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -413,13 +413,16 @@ class HTML_QuickForm_element extends HTML_Common
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$replacedName = str_replace(
|
$parts = explode('[', $elementName);
|
||||||
array('\\', '\'', ']', '['),
|
$ref = $values;
|
||||||
array('\\\\', '\\\'', '', "']['"),
|
foreach ($parts as $i => $part) {
|
||||||
$elementName
|
$key = 0 === $i ? $part : rtrim($part, ']');
|
||||||
);
|
if (!isset($ref[$key])) {
|
||||||
$myVar = "['$replacedName']";
|
return null;
|
||||||
return eval("return (isset(\$values$myVar)) ? \$values$myVar : null;");
|
}
|
||||||
|
$ref = $ref[$key];
|
||||||
|
}
|
||||||
|
return $ref;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -531,22 +534,33 @@ class HTML_QuickForm_element extends HTML_Common
|
|||||||
{
|
{
|
||||||
if (null === $value) {
|
if (null === $value) {
|
||||||
return null;
|
return null;
|
||||||
} elseif (!$assoc) {
|
|
||||||
return $value;
|
|
||||||
} else {
|
|
||||||
$name = $this->getName();
|
|
||||||
if (!strpos($name, '[')) {
|
|
||||||
return array($name => $value);
|
|
||||||
} else {
|
|
||||||
$valueAry = array();
|
|
||||||
$myIndex = "['" . str_replace(
|
|
||||||
array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"),
|
|
||||||
$name
|
|
||||||
) . "']";
|
|
||||||
eval("\$valueAry$myIndex = \$value;");
|
|
||||||
return $valueAry;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$assoc) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name = $this->getName();
|
||||||
|
|
||||||
|
if (!strpos($name, '[')) {
|
||||||
|
return array($name => $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$valueAry = [];
|
||||||
|
$parts = explode('[', $name);
|
||||||
|
$ref = &$valueAry;
|
||||||
|
|
||||||
|
foreach ($parts as $i => $part) {
|
||||||
|
$key = 0 === $i ? $part : rtrim($part, ']');
|
||||||
|
if (!isset($ref[$key]) || !is_array($ref[$key])) {
|
||||||
|
$ref[$key] = [];
|
||||||
|
}
|
||||||
|
$ref = &$ref[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
$ref = $value;
|
||||||
|
|
||||||
|
return $valueAry;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -223,27 +223,47 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
|
|||||||
$elementName = $this->getName();
|
$elementName = $this->getName();
|
||||||
if (isset($_FILES[$elementName])) {
|
if (isset($_FILES[$elementName])) {
|
||||||
return $_FILES[$elementName];
|
return $_FILES[$elementName];
|
||||||
} elseif (false !== ($pos = strpos($elementName, '['))) {
|
|
||||||
$base = str_replace(
|
|
||||||
array('\\', '\''), array('\\\\', '\\\''),
|
|
||||||
substr($elementName, 0, $pos)
|
|
||||||
);
|
|
||||||
$idx = "['" . str_replace(
|
|
||||||
array('\\', '\'', ']', '['), array('\\\\', '\\\'', '', "']['"),
|
|
||||||
substr($elementName, $pos + 1, -1)
|
|
||||||
) . "']";
|
|
||||||
$props = array('name', 'type', 'size', 'tmp_name', 'error');
|
|
||||||
$code = "if (!isset(\$_FILES['{$base}']['name']{$idx})) {\n" .
|
|
||||||
" return null;\n" .
|
|
||||||
"} else {\n" .
|
|
||||||
" \$value = array();\n";
|
|
||||||
foreach ($props as $prop) {
|
|
||||||
$code .= " \$value['{$prop}'] = \$_FILES['{$base}']['{$prop}']{$idx};\n";
|
|
||||||
}
|
|
||||||
return eval($code . " return \$value;\n}\n");
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (false !== ($pos = strpos($elementName, '['))) {
|
||||||
|
$base = substr($elementName, 0, $pos);
|
||||||
|
if (!isset($_FILES[$base])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$innerStr = substr($elementName, $pos + 1, -1);
|
||||||
|
$idxKeys = '' !== $innerStr ? explode('][', $innerStr) : [];
|
||||||
|
$nameEntry = $_FILES[$base]['name'] ?? null;
|
||||||
|
|
||||||
|
foreach ($idxKeys as $key) {
|
||||||
|
if (!is_array($nameEntry) || !array_key_exists($key, $nameEntry)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$nameEntry = $nameEntry[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
$props = array('name', 'type', 'size', 'tmp_name', 'error');
|
||||||
|
$value = [];
|
||||||
|
|
||||||
|
foreach ($props as $prop) {
|
||||||
|
$data = $_FILES[$base][$prop] ?? null;
|
||||||
|
|
||||||
|
foreach ($idxKeys as $key) {
|
||||||
|
if (!is_array($data) || !array_key_exists($key, $data)) {
|
||||||
|
$data = null;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
$data = $data[$key];
|
||||||
|
}
|
||||||
|
$value[$prop] = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -436,6 +436,9 @@ class SortableTable extends HTML_Table
|
|||||||
name="form_'.$this->table_name.'"
|
name="form_'.$this->table_name.'"
|
||||||
class="form-search"
|
class="form-search"
|
||||||
method="post" action="'.api_get_self().'?'.$params.'" >';
|
method="post" action="'.api_get_self().'?'.$params.'" >';
|
||||||
|
// CSRF token for the bulk-action POST. Kept in the request body only
|
||||||
|
// (not the action URL) to avoid leaking it through Referer/logs.
|
||||||
|
$html .= '<input type="hidden" name="sec_token" value="'.Security::get_existing_token().'" />';
|
||||||
}
|
}
|
||||||
|
|
||||||
$html .= '<div class="table-responsive">'.$content.'</div>';
|
$html .= '<div class="table-responsive">'.$content.'</div>';
|
||||||
|
|||||||
@@ -15,6 +15,69 @@ class SubLanguageManager
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that a variable name is a safe PHP identifier.
|
||||||
|
*
|
||||||
|
* @param string $variableName The variable name to validate
|
||||||
|
*
|
||||||
|
* @return bool True if the variable name matches /^[a-zA-Z_][a-zA-Z0-9_]*$/
|
||||||
|
*/
|
||||||
|
public static function isValidLanguageVariable($variableName)
|
||||||
|
{
|
||||||
|
return !empty($variableName) && preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $variableName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that a language folder name is safe for filesystem use.
|
||||||
|
* Rejects path traversal sequences, slashes, null bytes, and non-alphanumeric
|
||||||
|
* characters other than underscores and hyphens.
|
||||||
|
*
|
||||||
|
* @param string $folderName The folder name to validate
|
||||||
|
*
|
||||||
|
* @return bool True if the folder name is safe
|
||||||
|
*/
|
||||||
|
public static function isValidLanguageFolderName($folderName)
|
||||||
|
{
|
||||||
|
if (empty($folderName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($folderName, "\0") !== false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($folderName, '/') !== false ||
|
||||||
|
strpos($folderName, '\\') !== false ||
|
||||||
|
strpos($folderName, '..') !== false
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (bool) preg_match('/^[a-zA-Z0-9_\-]+$/', $folderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate that a file path is within the language directory.
|
||||||
|
*
|
||||||
|
* @param string $path The path to validate
|
||||||
|
*
|
||||||
|
* @return bool True if the path is safely within SYS_LANG_PATH
|
||||||
|
*/
|
||||||
|
public static function isPathInsideLangDir($path)
|
||||||
|
{
|
||||||
|
$langDir = realpath(api_get_path(SYS_LANG_PATH));
|
||||||
|
if ($langDir === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$realPath = realpath(dirname($path));
|
||||||
|
if ($realPath === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strpos($realPath, $langDir) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the languages.
|
* Get all the languages.
|
||||||
*
|
*
|
||||||
@@ -124,6 +187,13 @@ class SubLanguageManager
|
|||||||
*/
|
*/
|
||||||
public static function get_all_language_variable_in_file(string $system_path_file): array
|
public static function get_all_language_variable_in_file(string $system_path_file): array
|
||||||
{
|
{
|
||||||
|
// Validate the file is inside the lang directory before including it
|
||||||
|
$langDir = realpath(api_get_path(SYS_LANG_PATH));
|
||||||
|
$realFile = realpath($system_path_file);
|
||||||
|
if ($langDir === false || $realFile === false || strpos($realFile, $langDir) !== 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
include $system_path_file;
|
include $system_path_file;
|
||||||
@@ -135,6 +205,8 @@ class SubLanguageManager
|
|||||||
unset($variables['system_path_file']);
|
unset($variables['system_path_file']);
|
||||||
unset($variables['get_as_string_index']);
|
unset($variables['get_as_string_index']);
|
||||||
unset($variables['php_errormsg']);
|
unset($variables['php_errormsg']);
|
||||||
|
unset($variables['langDir']);
|
||||||
|
unset($variables['realFile']);
|
||||||
|
|
||||||
return $variables;
|
return $variables;
|
||||||
}
|
}
|
||||||
@@ -148,6 +220,14 @@ class SubLanguageManager
|
|||||||
*/
|
*/
|
||||||
public static function add_file_in_language_directory($system_path_file)
|
public static function add_file_in_language_directory($system_path_file)
|
||||||
{
|
{
|
||||||
|
// Validate the target path is inside the lang directory
|
||||||
|
// Use dirname check since the file itself may not exist yet
|
||||||
|
$langDir = realpath(api_get_path(SYS_LANG_PATH));
|
||||||
|
$parentDir = realpath(dirname($system_path_file));
|
||||||
|
if ($langDir === false || $parentDir === false || strpos($parentDir, $langDir) !== 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$return_value = @file_put_contents($system_path_file, '<?php'.PHP_EOL);
|
$return_value = @file_put_contents($system_path_file, '<?php'.PHP_EOL);
|
||||||
|
|
||||||
return $return_value;
|
return $return_value;
|
||||||
@@ -164,10 +244,22 @@ class SubLanguageManager
|
|||||||
*/
|
*/
|
||||||
public static function write_data_in_file($path_file, $new_term, $new_variable)
|
public static function write_data_in_file($path_file, $new_term, $new_variable)
|
||||||
{
|
{
|
||||||
|
// Validate variable name is a safe PHP identifier
|
||||||
|
if (!self::isValidLanguageVariable($new_variable)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the target path is inside the lang directory
|
||||||
|
if (!self::isPathInsideLangDir($path_file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Replace double quotes to avoid parse errors
|
// Replace double quotes to avoid parse errors
|
||||||
$new_term = addcslashes($new_term, "\$\"\\");
|
$new_term = addcslashes($new_term, "\$\"\\");
|
||||||
// Replace new line signs to avoid parse errors - see #6773
|
// Replace new line signs to avoid parse errors - see #6773
|
||||||
$new_term = str_replace("\n", "\\n", $new_term);
|
$new_term = str_replace("\n", "\\n", $new_term);
|
||||||
|
// Strip null bytes
|
||||||
|
$new_term = str_replace("\0", "", $new_term);
|
||||||
|
|
||||||
$return_value = false;
|
$return_value = false;
|
||||||
$new_data = '$'.$new_variable.'="'.$new_term.'";'.PHP_EOL;
|
$new_data = '$'.$new_variable.'="'.$new_term.'";'.PHP_EOL;
|
||||||
@@ -192,6 +284,11 @@ class SubLanguageManager
|
|||||||
if (empty($sub_language_dir)) {
|
if (empty($sub_language_dir)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!self::isValidLanguageFolderName($sub_language_dir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$dir = api_get_path(SYS_LANG_PATH).$sub_language_dir;
|
$dir = api_get_path(SYS_LANG_PATH).$sub_language_dir;
|
||||||
if (is_dir($dir)) {
|
if (is_dir($dir)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -249,6 +346,11 @@ class SubLanguageManager
|
|||||||
if (empty($sub_language_dir)) {
|
if (empty($sub_language_dir)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!self::isValidLanguageFolderName($sub_language_dir)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
$dir = api_get_path(SYS_LANG_PATH).$sub_language_dir;
|
$dir = api_get_path(SYS_LANG_PATH).$sub_language_dir;
|
||||||
if (!is_dir($dir)) {
|
if (!is_dir($dir)) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -7887,6 +7887,11 @@ class Tracking
|
|||||||
$debug = false
|
$debug = false
|
||||||
) {
|
) {
|
||||||
// Begin with the import process
|
// Begin with the import process
|
||||||
|
if (empty($course_info)) {
|
||||||
|
echo Display::return_message(get_lang('CourseNotFound'), 'error');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$origin_course_code = $course_info['code'];
|
$origin_course_code = $course_info['code'];
|
||||||
$course_id = $course_info['real_id'];
|
$course_id = $course_info['real_id'];
|
||||||
$user_id = (int) $user_id;
|
$user_id = (int) $user_id;
|
||||||
@@ -8075,7 +8080,7 @@ class Tracking
|
|||||||
// 4c. track_e_access_complete
|
// 4c. track_e_access_complete
|
||||||
$sql = "SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'track_e_access_complete'";
|
$sql = "SELECT count(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'track_e_access_complete'";
|
||||||
$res = Database::query($sql);
|
$res = Database::query($sql);
|
||||||
$row = Database::fetch_row($result);
|
$row = Database::fetch_row($res);
|
||||||
if ($row[0] > 0) {
|
if ($row[0] > 0) {
|
||||||
$sql = "SELECT id FROM $TBL_TRACK_E_ACCESS_COMPLETE
|
$sql = "SELECT id FROM $TBL_TRACK_E_ACCESS_COMPLETE
|
||||||
WHERE
|
WHERE
|
||||||
@@ -8186,8 +8191,8 @@ class Tracking
|
|||||||
$origin_session_id
|
$origin_session_id
|
||||||
);
|
);
|
||||||
$result_message['LP_VIEW'][$data['lp_id']] = [
|
$result_message['LP_VIEW'][$data['lp_id']] = [
|
||||||
'score' => $score,
|
'score' => is_null($score) ? 0 : $score,
|
||||||
'progress' => $progress,
|
'progress' => ($progress === false || is_null($progress)) ? 0 : $progress,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8227,8 +8232,8 @@ class Tracking
|
|||||||
$new_session_id
|
$new_session_id
|
||||||
);
|
);
|
||||||
$result_message_compare['LP_VIEW'][$data['lp_id']] = [
|
$result_message_compare['LP_VIEW'][$data['lp_id']] = [
|
||||||
'score' => $score,
|
'score' => is_null($score) ? 0 : $score,
|
||||||
'progress' => $progress,
|
'progress' => ($progress === false || is_null($progress)) ? 0 : $progress,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8659,7 +8664,7 @@ class Tracking
|
|||||||
WHERE login_id = $iIdLastConnection";
|
WHERE login_id = $iIdLastConnection";
|
||||||
$qLogoutDate = Database::query($sql);
|
$qLogoutDate = Database::query($sql);
|
||||||
$resLogoutDate = convert_sql_date(Database::result($qLogoutDate, 0, 'logout_date'));
|
$resLogoutDate = convert_sql_date(Database::result($qLogoutDate, 0, 'logout_date'));
|
||||||
$lifeTime = api_get_configuration_value('session_lifetime');
|
$lifeTime = (int) api_get_configuration_value('session_lifetime');
|
||||||
|
|
||||||
if ($resLogoutDate < time() - $lifeTime) {
|
if ($resLogoutDate < time() - $lifeTime) {
|
||||||
// it isn't, we should create a fresh entry
|
// it isn't, we should create a fresh entry
|
||||||
|
|||||||
@@ -305,6 +305,21 @@ class UserManager
|
|||||||
$emailTemplate = [],
|
$emailTemplate = [],
|
||||||
$redirectToURLAfterLogin = ''
|
$redirectToURLAfterLogin = ''
|
||||||
) {
|
) {
|
||||||
|
// Defense in depth against privilege mass-assignment (CWE-915):
|
||||||
|
// when the effective requester is anonymous AND no creator was supplied
|
||||||
|
// (the self-registration path), never allow elevated platform roles to
|
||||||
|
// be assigned. Only STUDENT and COURSEMANAGER are acceptable for
|
||||||
|
// self-service flows. Trusted server-to-server callers that pass an
|
||||||
|
// explicit $creatorId (e.g. the secret-key SOAP WSCreateUsers service,
|
||||||
|
// which runs without a session) are intentionally exempt.
|
||||||
|
if (empty($creatorId)
|
||||||
|
&& api_is_anonymous()
|
||||||
|
&& !in_array((int) $status, [STUDENT, COURSEMANAGER], true)
|
||||||
|
) {
|
||||||
|
$status = STUDENT;
|
||||||
|
$isAdmin = false;
|
||||||
|
}
|
||||||
|
|
||||||
$creatorId = empty($creatorId) ? api_get_user_id() : 0;
|
$creatorId = empty($creatorId) ? api_get_user_id() : 0;
|
||||||
$creatorInfo = api_get_user_info($creatorId);
|
$creatorInfo = api_get_user_info($creatorId);
|
||||||
$creatorEmail = isset($creatorInfo['email']) ? $creatorInfo['email'] : '';
|
$creatorEmail = isset($creatorInfo['email']) ? $creatorInfo['email'] : '';
|
||||||
|
|||||||
@@ -112,13 +112,17 @@ class Rest extends WebService
|
|||||||
public const GET_COURSES = 'get_courses';
|
public const GET_COURSES = 'get_courses';
|
||||||
public const GET_COURSES_FROM_EXTRA_FIELD = 'get_courses_from_extra_field';
|
public const GET_COURSES_FROM_EXTRA_FIELD = 'get_courses_from_extra_field';
|
||||||
public const SAVE_COURSE = 'save_course';
|
public const SAVE_COURSE = 'save_course';
|
||||||
|
public const UPDATE_COURSE = 'update_course';
|
||||||
public const DELETE_COURSE = 'delete_course';
|
public const DELETE_COURSE = 'delete_course';
|
||||||
public const GET_SESSION_FROM_EXTRA_FIELD = 'get_session_from_extra_field';
|
public const GET_SESSION_FROM_EXTRA_FIELD = 'get_session_from_extra_field';
|
||||||
|
public const GET_SESSION_INFO = 'session_info';
|
||||||
public const GET_SESSION_INFO_FROM_EXTRA_FIELD = 'get_session_info_from_extra_field';
|
public const GET_SESSION_INFO_FROM_EXTRA_FIELD = 'get_session_info_from_extra_field';
|
||||||
public const SAVE_SESSION = 'save_session';
|
public const SAVE_SESSION = 'save_session';
|
||||||
public const CREATE_SESSION_FROM_MODEL = 'create_session_from_model';
|
public const CREATE_SESSION_FROM_MODEL = 'create_session_from_model';
|
||||||
public const UPDATE_SESSION = 'update_session';
|
public const UPDATE_SESSION = 'update_session';
|
||||||
|
public const DELETE_SESSION = 'delete_session';
|
||||||
public const GET_SESSIONS = 'get_sessions';
|
public const GET_SESSIONS = 'get_sessions';
|
||||||
|
public const GET_COURSE_SESSIONS = 'get_course_sessions';
|
||||||
|
|
||||||
public const SUBSCRIBE_USER_TO_COURSE = 'subscribe_user_to_course';
|
public const SUBSCRIBE_USER_TO_COURSE = 'subscribe_user_to_course';
|
||||||
public const SUBSCRIBE_USER_TO_COURSE_PASSWORD = 'subscribe_user_to_course_password';
|
public const SUBSCRIBE_USER_TO_COURSE_PASSWORD = 'subscribe_user_to_course_password';
|
||||||
@@ -1760,6 +1764,40 @@ class Rest extends WebService
|
|||||||
return $shortList;
|
return $shortList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSessionsByCourse(): array
|
||||||
|
{
|
||||||
|
self::protectAdminEndpoint();
|
||||||
|
|
||||||
|
$sessions = SessionManager::get_session_by_course($this->course->getId());
|
||||||
|
$extraFieldValues = new ExtraFieldValue('session');
|
||||||
|
$filter = ['variable', 'value', 'display_text'];
|
||||||
|
$list = [];
|
||||||
|
|
||||||
|
foreach ($sessions as $sessionInfo) {
|
||||||
|
$session = api_get_session_info($sessionInfo['id']);
|
||||||
|
|
||||||
|
if (empty($session)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$extraFields = $extraFieldValues->getAllValuesByItem($session['id']);
|
||||||
|
$list[] = [
|
||||||
|
'id' => $session['id'],
|
||||||
|
'name' => $session['name'],
|
||||||
|
'access_start_date' => $session['access_start_date'],
|
||||||
|
'access_end_date' => $session['access_end_date'],
|
||||||
|
'extra_fields' => array_map(
|
||||||
|
function ($item) use ($filter) {
|
||||||
|
return array_intersect_key($item, array_flip($filter));
|
||||||
|
},
|
||||||
|
$extraFields
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of groups with id, group_type, name, description, visibility.
|
* Returns an array of groups with id, group_type, name, description, visibility.
|
||||||
*
|
*
|
||||||
@@ -1913,6 +1951,118 @@ class Rest extends WebService
|
|||||||
return $results;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function updateCourse(ParameterBag $request): array
|
||||||
|
{
|
||||||
|
self::protectAdminEndpoint();
|
||||||
|
|
||||||
|
$courseId = $request->getInt('course_id');
|
||||||
|
$courseCode = $request->get('course_code');
|
||||||
|
|
||||||
|
if (!empty($courseCode)) {
|
||||||
|
$courseInfo = api_get_course_info($courseCode);
|
||||||
|
} elseif (!empty($courseId)) {
|
||||||
|
$courseInfo = api_get_course_info_by_id($courseId);
|
||||||
|
} else {
|
||||||
|
throw new Exception(get_lang('NoData'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($courseInfo)) {
|
||||||
|
throw new Exception(get_lang('NoCourse'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$realId = $courseInfo['real_id'];
|
||||||
|
$table = Database::get_main_table(TABLE_MAIN_COURSE);
|
||||||
|
$params = [];
|
||||||
|
|
||||||
|
$title = $request->get('title');
|
||||||
|
|
||||||
|
if (!is_null($title)) {
|
||||||
|
$params['title'] = $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
$language = $request->get('language');
|
||||||
|
|
||||||
|
if (!is_null($language)) {
|
||||||
|
$params['course_language'] = $language;
|
||||||
|
}
|
||||||
|
|
||||||
|
$visibility = $request->get('visibility');
|
||||||
|
|
||||||
|
if (!is_null($visibility)) {
|
||||||
|
if (!isset(Course::getStatusList()[(int) $visibility])) {
|
||||||
|
throw new Exception(get_lang('VisibilityCannotBeChanged'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$params['visibility'] = (int) $visibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
$diskQuota = $request->get('disk_quota');
|
||||||
|
|
||||||
|
if (!is_null($diskQuota)) {
|
||||||
|
$params['disk_quota'] = (int) $diskQuota;
|
||||||
|
}
|
||||||
|
|
||||||
|
$categoryCode = $request->get('category_code');
|
||||||
|
|
||||||
|
if (!is_null($categoryCode)) {
|
||||||
|
$params['category_code'] = $categoryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
$departmentName = $request->get('department_name');
|
||||||
|
|
||||||
|
if (!is_null($departmentName)) {
|
||||||
|
$params['department_name'] = $departmentName;
|
||||||
|
}
|
||||||
|
|
||||||
|
$departmentUrl = $request->get('department_url');
|
||||||
|
|
||||||
|
if (!is_null($departmentUrl)) {
|
||||||
|
$params['department_url'] = $departmentUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
$subscribe = $request->get('subscribe');
|
||||||
|
|
||||||
|
if (!is_null($subscribe)) {
|
||||||
|
$params['subscribe'] = (int) $subscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
$unsubscribe = $request->get('unsubscribe');
|
||||||
|
|
||||||
|
if (!is_null($unsubscribe)) {
|
||||||
|
$params['unsubscribe'] = (int) $unsubscribe;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($params)) {
|
||||||
|
Database::update($table, $params, ['id = ?' => $realId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$extraFields = array_filter(
|
||||||
|
$request->all(),
|
||||||
|
function ($key) {
|
||||||
|
return substr($key, 0, 6) === 'extra_';
|
||||||
|
},
|
||||||
|
ARRAY_FILTER_USE_KEY
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!empty($extraFields)) {
|
||||||
|
$extraFields['item_id'] = $realId;
|
||||||
|
$courseFieldValue = new ExtraFieldValue('course');
|
||||||
|
$courseFieldValue->saveFieldValues($extraFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
$updatedCourse = api_get_course_info_by_id($realId);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'message' => get_lang('Updated'),
|
||||||
|
'id' => $realId,
|
||||||
|
'course_code' => $updatedCourse['code'],
|
||||||
|
'course_title' => $updatedCourse['title'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param $userParam
|
* @param $userParam
|
||||||
*
|
*
|
||||||
@@ -2599,6 +2749,33 @@ class Rest extends WebService
|
|||||||
return (int) $sessionIdList[0]['item_id'];
|
return (int) $sessionIdList[0]['item_id'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSessionInfo(): array
|
||||||
|
{
|
||||||
|
self::protectAdminEndpoint();
|
||||||
|
|
||||||
|
$bundle = [
|
||||||
|
'id' => $this->session->getId(),
|
||||||
|
'name' => $this->session->getName(),
|
||||||
|
'access_start_date' => $this->session->getAccessStartDate()->format('Y-m-d H:i:s'),
|
||||||
|
'access_end_date' => $this->session->getAccessEndDate()
|
||||||
|
? $this->session->getAccessEndDate()->format('Y-m-d H:i:s')
|
||||||
|
: null,
|
||||||
|
];
|
||||||
|
$extraFieldValues = new ExtraFieldValue('session');
|
||||||
|
$extraFields = $extraFieldValues->getAllValuesByItem($this->session->getId());
|
||||||
|
// Only return these properties for each extra_field (the rest is not relevant to a webservice)
|
||||||
|
$filter = ['variable', 'value', 'display_text'];
|
||||||
|
$bundle['extra_fields'] = array_map(
|
||||||
|
function ($item) use ($filter) {
|
||||||
|
return array_intersect_key($item, array_flip($filter));
|
||||||
|
},
|
||||||
|
$extraFields
|
||||||
|
);
|
||||||
|
|
||||||
|
// return session details, including extra fields that have filter=1
|
||||||
|
return $bundle;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds the session which has a specific value in a specific extra field and return its details.
|
* Finds the session which has a specific value in a specific extra field and return its details.
|
||||||
*
|
*
|
||||||
@@ -3140,6 +3317,29 @@ class Rest extends WebService
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
public function deleteSession(int $sessionId): array
|
||||||
|
{
|
||||||
|
if (!SessionManager::cantEditSession($sessionId)) {
|
||||||
|
self::throwNotAllowedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$sessionInfo = api_get_session_info($sessionId);
|
||||||
|
|
||||||
|
if (empty($sessionInfo)) {
|
||||||
|
throw new Exception(get_lang('NoData'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = SessionManager::delete($sessionId);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'status' => $result,
|
||||||
|
'message' => $result ? get_lang('Deleted').': '.$sessionInfo['name'] : get_lang('Error'),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
public function checkConditionalLogin(): bool
|
public function checkConditionalLogin(): bool
|
||||||
{
|
{
|
||||||
$file = api_get_path(SYS_CODE_PATH).'auth/conditional_login/conditional_login.php';
|
$file = api_get_path(SYS_CODE_PATH).'auth/conditional_login/conditional_login.php';
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
/**
|
/**
|
||||||
* Variables used from the main/install/index.php.
|
* Variables used from the main/install/index.php.
|
||||||
*/
|
*/
|
||||||
$new_version = '1.11.38';
|
$new_version = '1.11.40';
|
||||||
$new_version_status = 'stable';
|
$new_version_status = 'stable';
|
||||||
$new_version_last_id = 0;
|
$new_version_last_id = 0;
|
||||||
$new_version_stable = true;
|
$new_version_stable = true;
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
+42
-33
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
/* For licensing terms, see /license.txt */
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Http\SafeHttp;
|
||||||
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
|
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
|
||||||
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
|
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
|
||||||
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
|
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
|
||||||
@@ -13443,43 +13444,51 @@ EOD;
|
|||||||
|
|
||||||
if ($protocolFixApplied == false) {
|
if ($protocolFixApplied == false) {
|
||||||
if (strpos(api_get_path(WEB_PATH), $host) === false) {
|
if (strpos(api_get_path(WEB_PATH), $host) === false) {
|
||||||
// Check X-Frame-Options
|
// Check X-Frame-Options through an SSRF guard (CWE-918): only
|
||||||
$ch = curl_init();
|
// probe public http(s) hosts. A target resolving to a
|
||||||
$options = [
|
// loopback/private/reserved/link-local address (incl. the cloud
|
||||||
CURLOPT_URL => $src,
|
// metadata endpoint) is left untouched, exactly as the legacy
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
// path behaved when the request failed.
|
||||||
CURLOPT_HEADER => true,
|
$safeIp = SafeHttp::resolveSafeIp($src);
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
|
||||||
CURLOPT_ENCODING => "",
|
|
||||||
CURLOPT_AUTOREFERER => true,
|
|
||||||
CURLOPT_CONNECTTIMEOUT => 120,
|
|
||||||
CURLOPT_TIMEOUT => 120,
|
|
||||||
CURLOPT_MAXREDIRS => 10,
|
|
||||||
];
|
|
||||||
|
|
||||||
$proxySettings = api_get_configuration_value('proxy_settings');
|
if (null !== $safeIp) {
|
||||||
if (!empty($proxySettings) &&
|
$ch = curl_init();
|
||||||
isset($proxySettings['curl_setopt_array'])
|
$options = [
|
||||||
) {
|
CURLOPT_URL => $src,
|
||||||
$options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
$options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
|
CURLOPT_HEADER => true,
|
||||||
}
|
CURLOPT_ENCODING => "",
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 120,
|
||||||
|
CURLOPT_TIMEOUT => 120,
|
||||||
|
];
|
||||||
|
// Forbid redirects/non-HTTP schemes and pin the validated IP
|
||||||
|
// to defeat DNS rebinding; restore TLS verification.
|
||||||
|
$options += SafeHttp::secureCurlOptions($src, $safeIp);
|
||||||
|
|
||||||
curl_setopt_array($ch, $options);
|
$proxySettings = api_get_configuration_value('proxy_settings');
|
||||||
$response = curl_exec($ch);
|
if (!empty($proxySettings) &&
|
||||||
$httpCode = curl_getinfo($ch);
|
isset($proxySettings['curl_setopt_array'])
|
||||||
$headers = substr($response, 0, $httpCode['header_size']);
|
) {
|
||||||
|
$options[CURLOPT_PROXY] = $proxySettings['curl_setopt_array']['CURLOPT_PROXY'];
|
||||||
|
$options[CURLOPT_PROXYPORT] = $proxySettings['curl_setopt_array']['CURLOPT_PROXYPORT'];
|
||||||
|
}
|
||||||
|
|
||||||
$error = false;
|
curl_setopt_array($ch, $options);
|
||||||
if (stripos($headers, 'X-Frame-Options: DENY') > -1
|
$response = curl_exec($ch);
|
||||||
//|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
|
$httpCode = curl_getinfo($ch);
|
||||||
) {
|
$headers = substr($response, 0, $httpCode['header_size']);
|
||||||
$error = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($error) {
|
$error = false;
|
||||||
Session::write('x_frame_source', $src);
|
if (stripos($headers, 'X-Frame-Options: DENY') > -1
|
||||||
$src = 'blank.php?error=x_frames_options';
|
//|| stripos($headers, 'X-Frame-Options: SAMEORIGIN') > -1
|
||||||
|
) {
|
||||||
|
$error = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($error) {
|
||||||
|
Session::write('x_frame_source', $src);
|
||||||
|
$src = 'blank.php?error=x_frames_options';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ require_once __DIR__.'/lp_initialize_item.inc.php';
|
|||||||
|
|
||||||
echo initialize_item(
|
echo initialize_item(
|
||||||
$_POST['lid'],
|
$_POST['lid'],
|
||||||
$_POST['uid'],
|
api_get_user_id(),
|
||||||
$_POST['vid'],
|
$_POST['vid'],
|
||||||
$_POST['iid']
|
$_POST['iid']
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
$use_anonymous = true;
|
$use_anonymous = true;
|
||||||
require_once __DIR__.'/../inc/global.inc.php';
|
require_once __DIR__.'/../inc/global.inc.php';
|
||||||
|
|
||||||
|
api_protect_course_script();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes an item's new values into the database and returns the operation result.
|
* Writes an item's new values into the database and returns the operation result.
|
||||||
*
|
*
|
||||||
@@ -55,7 +57,7 @@ if (isset($_REQUEST['objectives'])) {
|
|||||||
|
|
||||||
echo save_objectives(
|
echo save_objectives(
|
||||||
$_REQUEST['lid'],
|
$_REQUEST['lid'],
|
||||||
$_REQUEST['uid'],
|
api_get_user_id(),
|
||||||
$_REQUEST['vid'],
|
$_REQUEST['vid'],
|
||||||
$_REQUEST['iid'],
|
$_REQUEST['iid'],
|
||||||
$objectives
|
$objectives
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ use ChamiloSession as Session;
|
|||||||
$use_anonymous = true;
|
$use_anonymous = true;
|
||||||
require_once __DIR__.'/../inc/global.inc.php';
|
require_once __DIR__.'/../inc/global.inc.php';
|
||||||
|
|
||||||
|
api_protect_course_script();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get one item's details.
|
* Get one item's details.
|
||||||
*
|
*
|
||||||
@@ -286,7 +288,7 @@ function switch_item_details($lp_id, $user_id, $view_id, $current_item, $next_it
|
|||||||
|
|
||||||
echo switch_item_details(
|
echo switch_item_details(
|
||||||
$_REQUEST['lid'],
|
$_REQUEST['lid'],
|
||||||
$_REQUEST['uid'],
|
api_get_user_id(),
|
||||||
$_REQUEST['vid'],
|
$_REQUEST['vid'],
|
||||||
$_REQUEST['iid'],
|
$_REQUEST['iid'],
|
||||||
$_REQUEST['next']
|
$_REQUEST['next']
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class scormItem extends learnpathItem
|
|||||||
case 'title':
|
case 'title':
|
||||||
$tmp_children = $child->childNodes;
|
$tmp_children = $child->childNodes;
|
||||||
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
|
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
|
||||||
$this->title = $child->firstChild->nodeValue;
|
$this->title = Security::remove_XSS($child->firstChild->nodeValue);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'max_score':
|
case 'max_score':
|
||||||
|
|||||||
+29
-33
@@ -48,51 +48,47 @@ $allowToQualify = api_is_allowed_to_edit(null, true) ||
|
|||||||
api_is_drh() ||
|
api_is_drh() ||
|
||||||
api_is_student_boss();
|
api_is_student_boss();
|
||||||
|
|
||||||
$allowedToTrackUser =
|
// Access to a student's tracking is granted on two levels:
|
||||||
|
// 1) Wide scope: platform/session admins, HR managers (DRH) and student bosses
|
||||||
|
// may view any student's tracking data.
|
||||||
|
// 2) Teachers, coaches and course tutors may only view a student they have a
|
||||||
|
// real relationship with: a shared course (as teacher), a shared session (as
|
||||||
|
// coach), or a course they tutor where the student is enrolled. This closes
|
||||||
|
// the hole where any teacher could read any student's data regardless of
|
||||||
|
// enrollment.
|
||||||
|
$hasWideTrackingScope =
|
||||||
api_is_platform_admin(true, true) ||
|
api_is_platform_admin(true, true) ||
|
||||||
api_is_allowed_to_edit(null, true) ||
|
|
||||||
api_is_session_admin() ||
|
api_is_session_admin() ||
|
||||||
api_is_drh() ||
|
api_is_drh() ||
|
||||||
api_is_student_boss() ||
|
api_is_student_boss();
|
||||||
api_is_course_admin() ||
|
|
||||||
api_is_teacher()
|
|
||||||
;
|
|
||||||
|
|
||||||
$em = Database::getManager();
|
$em = Database::getManager();
|
||||||
$itemRepo = $em->getRepository(CItemProperty::class);
|
$itemRepo = $em->getRepository(CItemProperty::class);
|
||||||
|
|
||||||
if (false === $allowedToTrackUser && !empty($courseInfo)) {
|
$tracksThisStudent = false;
|
||||||
if (empty($sessionId)) {
|
if (!$hasWideTrackingScope && !empty($student_id)) {
|
||||||
$isTeacher = CourseManager::is_course_teacher(
|
$tracksThisStudent =
|
||||||
|
UserManager::isTeacherOfStudent(api_get_user_id(), $student_id) ||
|
||||||
|
Tracking::is_allowed_to_coach_student(api_get_user_id(), $student_id);
|
||||||
|
|
||||||
|
// A course tutor keeps access, but only to students enrolled in the course
|
||||||
|
// they tutor (not to any student, as the previous check allowed).
|
||||||
|
if (!$tracksThisStudent && !empty($courseInfo)) {
|
||||||
|
$isCourseTutor = 1 === (int) CourseManager::get_tutor_in_course_status(
|
||||||
api_get_user_id(),
|
api_get_user_id(),
|
||||||
$courseInfo['code']
|
$courseInfo['real_id']
|
||||||
);
|
);
|
||||||
|
$studentInCourse = CourseManager::is_user_subscribed_in_course(
|
||||||
if ($isTeacher) {
|
$student_id,
|
||||||
$allowedToTrackUser = true;
|
$courseInfo['code'],
|
||||||
} else {
|
!empty($sessionId),
|
||||||
// Check if the user is tutor of the course
|
$sessionId
|
||||||
$userCourseStatus = CourseManager::get_tutor_in_course_status(
|
);
|
||||||
api_get_user_id(),
|
$tracksThisStudent = $isCourseTutor && $studentInCourse;
|
||||||
$courseInfo['real_id']
|
|
||||||
);
|
|
||||||
if ($userCourseStatus == 1) {
|
|
||||||
$allowedToTrackUser = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$coach = api_is_coach($sessionId, $courseInfo['real_id']);
|
|
||||||
|
|
||||||
if ($coach) {
|
|
||||||
$allowedToTrackUser = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$allowedToTrackUser) {
|
if (empty($student_id) || (!$hasWideTrackingScope && !$tracksThisStudent)) {
|
||||||
api_not_allowed(true);
|
|
||||||
}
|
|
||||||
if (empty($student_id)) {
|
|
||||||
api_not_allowed(true);
|
api_not_allowed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ $httpRequest = HttpRequest::createFromGlobals();
|
|||||||
$action = $httpRequest->query->get('action', 'list');
|
$action = $httpRequest->query->get('action', 'list');
|
||||||
|
|
||||||
// It validates the management of categories will be only for admins
|
// It validates the management of categories will be only for admins
|
||||||
if (in_array($action, ['list_categories', 'add_category', 'edit_category']) && !api_is_platform_admin()) {
|
if (in_array($action, ['list_categories', 'add_category', 'edit_category', 'translate_category']) && !api_is_platform_admin()) {
|
||||||
api_not_allowed(true);
|
api_not_allowed(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
/* For licensing terms, see /license.txt */
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Essence\SafeEssenceHttpClient;
|
||||||
use Chamilo\CoreBundle\Entity\Course;
|
use Chamilo\CoreBundle\Entity\Course;
|
||||||
use Chamilo\CoreBundle\Entity\ExtraField;
|
use Chamilo\CoreBundle\Entity\ExtraField;
|
||||||
use Chamilo\CoreBundle\Entity\Repository\SequenceResourceRepository;
|
use Chamilo\CoreBundle\Entity\Repository\SequenceResourceRepository;
|
||||||
@@ -238,7 +239,11 @@ $redirectToSession = 1 == $coursesCount && $redirectToSession
|
|||||||
|
|
||||||
$template->assign('redirect_to_session', $redirectToSession);
|
$template->assign('redirect_to_session', $redirectToSession);
|
||||||
$template->assign('courses', $courses);
|
$template->assign('courses', $courses);
|
||||||
$essence = Essence\Essence::instance();
|
// SSRF protection: route Essence's server-side OEmbed/OpenGraph fetches of the
|
||||||
|
// teacher-set video URL through an IP-filtered HTTP client.
|
||||||
|
$essence = Essence\Essence::instance(
|
||||||
|
['Http' => new SafeEssenceHttpClient()]
|
||||||
|
);
|
||||||
$template->assign('essence', $essence);
|
$template->assign('essence', $essence);
|
||||||
$template->assign(
|
$template->assign(
|
||||||
'session_extra_fields',
|
'session_extra_fields',
|
||||||
|
|||||||
@@ -802,6 +802,11 @@ try {
|
|||||||
Event::addEvent(LOG_WS.$action, 'course_id', $data['id']);
|
Event::addEvent(LOG_WS.$action, 'course_id', $data['id']);
|
||||||
$restResponse->setData($data);
|
$restResponse->setData($data);
|
||||||
break;
|
break;
|
||||||
|
case Rest::UPDATE_COURSE:
|
||||||
|
$data = $restApi->updateCourse($httpRequest->request);
|
||||||
|
Event::addEvent(LOG_WS.$action, 'course_id', $data['id']);
|
||||||
|
$restResponse->setData($data);
|
||||||
|
break;
|
||||||
case Rest::DELETE_COURSE:
|
case Rest::DELETE_COURSE:
|
||||||
if (!api_is_platform_admin()) {
|
if (!api_is_platform_admin()) {
|
||||||
throw new Exception(get_lang('NotAllowed'));
|
throw new Exception(get_lang('NotAllowed'));
|
||||||
@@ -827,6 +832,13 @@ try {
|
|||||||
Event::addEvent(LOG_WS.$action, 'course_id', $courseId);
|
Event::addEvent(LOG_WS.$action, 'course_id', $courseId);
|
||||||
$restResponse->setData(['status' => $result]);
|
$restResponse->setData(['status' => $result]);
|
||||||
break;
|
break;
|
||||||
|
case Rest::GET_SESSION_INFO:
|
||||||
|
$sessionInfo = $restApi->getSessionInfo();
|
||||||
|
|
||||||
|
Event::addEvent(LOG_WS.$action, 'course_id', $sessionInfo['id']);
|
||||||
|
|
||||||
|
$restResponse->setData($sessionInfo);
|
||||||
|
break;
|
||||||
case Rest::GET_SESSION_FROM_EXTRA_FIELD:
|
case Rest::GET_SESSION_FROM_EXTRA_FIELD:
|
||||||
$fieldName = trim($httpRequest->request->get('field_name'));
|
$fieldName = trim($httpRequest->request->get('field_name'));
|
||||||
$fieldValue = trim($httpRequest->request->get('field_value'));
|
$fieldValue = trim($httpRequest->request->get('field_value'));
|
||||||
@@ -870,6 +882,12 @@ try {
|
|||||||
Event::addEvent(LOG_WS.$action, 'session_id', $data['id_session']);
|
Event::addEvent(LOG_WS.$action, 'session_id', $data['id_session']);
|
||||||
$restResponse->setData($data);
|
$restResponse->setData($data);
|
||||||
break;
|
break;
|
||||||
|
case Rest::DELETE_SESSION:
|
||||||
|
$sessionId = (int) ($_REQUEST['session_id'] ?? 0);
|
||||||
|
$data = $restApi->deleteSession($sessionId);
|
||||||
|
Event::addEvent(LOG_WS.$action, 'session_id', $sessionId);
|
||||||
|
$restResponse->setData($data);
|
||||||
|
break;
|
||||||
case Rest::SUBSCRIBE_USER_TO_COURSE:
|
case Rest::SUBSCRIBE_USER_TO_COURSE:
|
||||||
$data = $restApi->subscribeUserToCourse($_POST);
|
$data = $restApi->subscribeUserToCourse($_POST);
|
||||||
Event::addEvent(LOG_WS.$action, 'course_id-user_id', (int) $_POST['course_id'].':'.(int) $_POST['user_id']);
|
Event::addEvent(LOG_WS.$action, 'course_id-user_id', (int) $_POST['course_id'].':'.(int) $_POST['user_id']);
|
||||||
@@ -906,6 +924,13 @@ try {
|
|||||||
$data = $restApi->getSessionsCampus($campusId, $getExtraFields);
|
$data = $restApi->getSessionsCampus($campusId, $getExtraFields);
|
||||||
$restResponse->setData($data);
|
$restResponse->setData($data);
|
||||||
break;
|
break;
|
||||||
|
case Rest::GET_COURSE_SESSIONS:
|
||||||
|
Event::addEvent(LOG_WS.$action, 'course', $course);
|
||||||
|
|
||||||
|
$restResponse->setData(
|
||||||
|
$restApi->getSessionsByCourse()
|
||||||
|
);
|
||||||
|
break;
|
||||||
case Rest::ADD_COURSES_SESSION:
|
case Rest::ADD_COURSES_SESSION:
|
||||||
$data = $restApi->addCoursesSession($_POST);
|
$data = $restApi->addCoursesSession($_POST);
|
||||||
Event::addEvent(
|
Event::addEvent(
|
||||||
|
|||||||
@@ -239,7 +239,10 @@ if (!empty($courses)) {
|
|||||||
$url .= '&status='.(int) $status;
|
$url .= '&status='.(int) $status;
|
||||||
}
|
}
|
||||||
if (!empty($values['work_parent_ids'])) {
|
if (!empty($values['work_parent_ids'])) {
|
||||||
$url .= '&work_parent_ids='.Security::remove_XSS(implode(',', $values['work_parent_ids']));
|
$values['work_parent_ids'] = array_filter(
|
||||||
|
array_map('intval', (array) $values['work_parent_ids'])
|
||||||
|
);
|
||||||
|
$url .= '&work_parent_ids='.implode(',', $values['work_parent_ids']);
|
||||||
}
|
}
|
||||||
if ($exportXls) {
|
if ($exportXls) {
|
||||||
exportPendingWorksToExcel($values);
|
exportPendingWorksToExcel($values);
|
||||||
|
|||||||
@@ -6562,11 +6562,16 @@ function exportPendingWorksToExcel($values)
|
|||||||
];
|
];
|
||||||
$tableXls[] = $headers;
|
$tableXls[] = $headers;
|
||||||
|
|
||||||
$courseId = $values['course'] ?? 0;
|
$courseId = (int) ($values['course'] ?? 0);
|
||||||
$status = $values['status'] ?? 0;
|
$status = $values['status'] ?? 0;
|
||||||
$whereCondition = '';
|
$whereCondition = '';
|
||||||
if (!empty($values['work_parent_ids'])) {
|
if (!empty($values['work_parent_ids'])) {
|
||||||
$whereCondition = ' parent_id IN('.implode(',', $values['work_parent_ids']).')';
|
$workParentIds = array_filter(
|
||||||
|
array_map('intval', (array) $values['work_parent_ids'])
|
||||||
|
);
|
||||||
|
if (!empty($workParentIds)) {
|
||||||
|
$whereCondition = ' parent_id IN('.implode(',', $workParentIds).')';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
$allWork = getAllWork(
|
$allWork = getAllWork(
|
||||||
null,
|
null,
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
*
|
*
|
||||||
* @package chamilo.buycourses_plugin
|
* @package chamilo.buycourses_plugin
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Essence\SafeEssenceHttpClient;
|
||||||
|
|
||||||
$cidReset = true;
|
$cidReset = true;
|
||||||
|
|
||||||
require_once '../../../main/inc/global.inc.php';
|
require_once '../../../main/inc/global.inc.php';
|
||||||
@@ -34,7 +37,14 @@ if (!$service['id']) {
|
|||||||
$template = new Template(false);
|
$template = new Template(false);
|
||||||
$template->assign('pageUrl', api_get_path(WEB_PATH)."service/{$serviceId}/information/");
|
$template->assign('pageUrl', api_get_path(WEB_PATH)."service/{$serviceId}/information/");
|
||||||
$template->assign('service', $service);
|
$template->assign('service', $service);
|
||||||
$template->assign('essence', Essence\Essence::instance());
|
// SSRF protection: route Essence's server-side OEmbed/OpenGraph fetches
|
||||||
|
// through an IP-filtered HTTP client.
|
||||||
|
$template->assign(
|
||||||
|
'essence',
|
||||||
|
Essence\Essence::instance(
|
||||||
|
['Http' => new SafeEssenceHttpClient()]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
$content = $template->fetch('buycourses/view/service_information.tpl');
|
$content = $template->fetch('buycourses/view/service_information.tpl');
|
||||||
|
|
||||||
|
|||||||
@@ -12,33 +12,94 @@ api_protect_admin_script();
|
|||||||
$plugin = CleanDeletedFilesPlugin::create();
|
$plugin = CleanDeletedFilesPlugin::create();
|
||||||
$action = isset($_REQUEST['a']) ? $_REQUEST['a'] : null;
|
$action = isset($_REQUEST['a']) ? $_REQUEST['a'] : null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a client-supplied path to a safe, canonical file path.
|
||||||
|
*
|
||||||
|
* Only regular files whose name contains "DELETED" and that live inside the
|
||||||
|
* directories the plugin actually scans (app/courses, app/upload) are allowed.
|
||||||
|
* Returns the canonical path on success, or null when the path is not allowed.
|
||||||
|
*
|
||||||
|
* @param string|null $path
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
function cleanDeletedFilesResolvePath($path)
|
||||||
|
{
|
||||||
|
if (!is_string($path) || "" === $path) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The front-end may send the path prefixed with the file:// scheme.
|
||||||
|
if (0 === strpos($path, "file://")) {
|
||||||
|
$path = substr($path, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
$realPath = realpath($path);
|
||||||
|
if (false === $realPath || !is_file($realPath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only files flagged as DELETED by Chamilo may be removed here.
|
||||||
|
if (false === strpos(basename($realPath), "DELETED")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$allowedDirs = [
|
||||||
|
realpath(api_get_path(SYS_PATH)."app/courses"),
|
||||||
|
realpath(api_get_path(SYS_PATH)."app/upload"),
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($allowedDirs as $allowedDir) {
|
||||||
|
if (false !== $allowedDir
|
||||||
|
&& 0 === strpos($realPath, $allowedDir.DIRECTORY_SEPARATOR)
|
||||||
|
) {
|
||||||
|
return $realPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
switch ($action) {
|
switch ($action) {
|
||||||
case 'delete-file':
|
case 'delete-file':
|
||||||
$path = isset($_REQUEST['path']) ? $_REQUEST['path'] : null;
|
$path = $_REQUEST['path'] ?? null;
|
||||||
if (empty($path)) {
|
if (empty($path)) {
|
||||||
echo json_encode(["status" => "false", "message" => $plugin->get_lang('ErrorEmptyPath')]);
|
echo json_encode(["status" => "false", "message" => $plugin->get_lang('ErrorEmptyPath')]);
|
||||||
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unlink($path)) {
|
$realPath = cleanDeletedFilesResolvePath($path);
|
||||||
|
|
||||||
|
if (null === $realPath) {
|
||||||
|
echo json_encode(["status" => "false", "message" => $plugin->get_lang('ErrorDeleteFile')]);
|
||||||
|
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlink($realPath)) {
|
||||||
Display::addFlash($plugin->get_lang("DeletedSuccess"), 'success');
|
Display::addFlash($plugin->get_lang("DeletedSuccess"), 'success');
|
||||||
echo json_encode(["status" => "true"]);
|
echo json_encode(["status" => "true"]);
|
||||||
} else {
|
} else {
|
||||||
echo json_encode(["status" => "false", "message" => $plugin->get_lang('ErrorDeleteFile')]);
|
echo json_encode(["status" => "false", "message" => $plugin->get_lang('ErrorDeleteFile')]);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'delete-files-list':
|
case 'delete-files-list':
|
||||||
$list = isset($_REQUEST['list']) ? $_REQUEST['list'] : [];
|
$list = $_REQUEST['list'] ?? [];
|
||||||
|
|
||||||
if (empty($list)) {
|
if (empty($list)) {
|
||||||
echo json_encode(["status" => "false", "message" => $plugin->get_lang('ErrorEmptyPath')]);
|
echo json_encode(["status" => "false", "message" => $plugin->get_lang('ErrorEmptyPath')]);
|
||||||
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($list as $value) {
|
foreach ($list as $value) {
|
||||||
if (empty($value)) {
|
$realPath = cleanDeletedFilesResolvePath($value);
|
||||||
continue;
|
|
||||||
|
if (null !== $realPath) {
|
||||||
|
unlink($realPath);
|
||||||
}
|
}
|
||||||
unlink($value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Display::addFlash($plugin->get_lang("DeletedSuccess"), 'success');
|
Display::addFlash($plugin->get_lang("DeletedSuccess"), 'success');
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
/* For license terms, see /license.txt */
|
/* For license terms, see /license.txt */
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Http\SafeHttp;
|
||||||
use Chamilo\CoreBundle\Entity\Course;
|
use Chamilo\CoreBundle\Entity\Course;
|
||||||
use Chamilo\CoreBundle\Entity\Session;
|
use Chamilo\CoreBundle\Entity\Session;
|
||||||
use Chamilo\CourseBundle\Entity\CTool;
|
use Chamilo\CourseBundle\Entity\CTool;
|
||||||
@@ -466,15 +467,25 @@ class ImsLtiPlugin extends Plugin
|
|||||||
*/
|
*/
|
||||||
public function getLaunchUrlFromCartridge($configUrl)
|
public function getLaunchUrlFromCartridge($configUrl)
|
||||||
{
|
{
|
||||||
|
// SSRF guard (CWE-918): only fetch public http(s) targets. Reject
|
||||||
|
// loopback/private/reserved/link-local hosts and the cloud metadata
|
||||||
|
// endpoint before issuing any request.
|
||||||
|
$safeIp = SafeHttp::resolveSafeIp($configUrl);
|
||||||
|
|
||||||
|
if (null === $safeIp) {
|
||||||
|
throw new Exception($this->get_lang('NoAccessToUrl'));
|
||||||
|
}
|
||||||
|
|
||||||
$options = [
|
$options = [
|
||||||
CURLOPT_CUSTOMREQUEST => 'GET',
|
CURLOPT_CUSTOMREQUEST => 'GET',
|
||||||
CURLOPT_POST => false,
|
CURLOPT_POST => false,
|
||||||
CURLOPT_RETURNTRANSFER => true,
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
CURLOPT_HEADER => false,
|
CURLOPT_HEADER => false,
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
|
||||||
CURLOPT_ENCODING => '',
|
CURLOPT_ENCODING => '',
|
||||||
CURLOPT_SSL_VERIFYPEER => false,
|
|
||||||
];
|
];
|
||||||
|
// Forbid redirects/non-HTTP schemes and pin the validated IP to defeat
|
||||||
|
// DNS rebinding; restore TLS verification.
|
||||||
|
$options += SafeHttp::secureCurlOptions($configUrl, $safeIp);
|
||||||
|
|
||||||
$ch = curl_init($configUrl);
|
$ch = curl_init($configUrl);
|
||||||
curl_setopt_array($ch, $options);
|
curl_setopt_array($ch, $options);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class TrackCourseAccessLoader implements LoaderInterface
|
|||||||
{
|
{
|
||||||
list($userId, $cId, $loginCourseDate, $ip, $sessionId) = array_values($incomingData);
|
list($userId, $cId, $loginCourseDate, $ip, $sessionId) = array_values($incomingData);
|
||||||
|
|
||||||
$sessionLifetime = api_get_configuration_value('session_lifetime');
|
$sessionLifetime = (int) api_get_configuration_value('session_lifetime');
|
||||||
|
|
||||||
/** @var \DateTime $time */
|
/** @var \DateTime $time */
|
||||||
$time = clone $loginCourseDate;
|
$time = clone $loginCourseDate;
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
* @licence http://www.gnu.org/licenses/gpl.txt
|
* @licence http://www.gnu.org/licenses/gpl.txt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Http\SafeHttp;
|
||||||
|
|
||||||
require_once __DIR__ . '/pens_controller.php';
|
require_once __DIR__ . '/pens_controller.php';
|
||||||
require_once __DIR__ . '/pens_package_handler.php';
|
require_once __DIR__ . '/pens_package_handler.php';
|
||||||
require_once __DIR__ . '/pens_exception.php';
|
require_once __DIR__ . '/pens_exception.php';
|
||||||
@@ -175,11 +177,22 @@ class PENSServer extends PENSController {
|
|||||||
$tmp = "/tmp";
|
$tmp = "/tmp";
|
||||||
}
|
}
|
||||||
$path_to_file = $tmp."/".$request->getFilename();
|
$path_to_file = $tmp."/".$request->getFilename();
|
||||||
|
// SSRF guard (CWE-918): block downloads from targets resolving to a
|
||||||
|
// loopback/private/reserved/link-local address (incl. the cloud metadata
|
||||||
|
// endpoint) or using a non-http(s) scheme, before opening any socket.
|
||||||
|
$packageUrl = $request->getPackageUrl();
|
||||||
|
$safeIp = SafeHttp::resolveSafeIp($packageUrl);
|
||||||
|
if (null === $safeIp) {
|
||||||
|
throw new PENSException(1310);
|
||||||
|
}
|
||||||
$fp = fopen($path_to_file, 'w');
|
$fp = fopen($path_to_file, 'w');
|
||||||
$ch = curl_init();
|
$ch = curl_init();
|
||||||
curl_setopt($ch, CURLOPT_URL, $request->getPackageUrl());
|
curl_setopt($ch, CURLOPT_URL, $packageUrl);
|
||||||
curl_setopt($ch, CURLOPT_HEADER, false);
|
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||||
curl_setopt($ch, CURLOPT_FILE, $fp);
|
curl_setopt($ch, CURLOPT_FILE, $fp);
|
||||||
|
// Forbid redirects/non-HTTP schemes and pin the validated IP to defeat
|
||||||
|
// DNS rebinding; keep TLS verification on.
|
||||||
|
curl_setopt_array($ch, SafeHttp::secureCurlOptions($packageUrl, $safeIp));
|
||||||
if(!is_null($request->getPackageUrlUserId())) {
|
if(!is_null($request->getPackageUrlUserId())) {
|
||||||
curl_setopt($ch, CURLOPT_USERPWD, $request->getPackageUrlUserId().":".$request->getPackageUrlPassword());
|
curl_setopt($ch, CURLOPT_USERPWD, $request->getPackageUrlUserId().":".$request->getPackageUrlPassword());
|
||||||
}
|
}
|
||||||
@@ -263,10 +276,20 @@ class PENSServer extends PENSController {
|
|||||||
} else {
|
} else {
|
||||||
$params = array_merge($request->getSendReceiptArray(), $response->getArray());
|
$params = array_merge($request->getSendReceiptArray(), $response->getArray());
|
||||||
}
|
}
|
||||||
|
// SSRF guard (CWE-918): only POST the alert/receipt callback
|
||||||
|
// to a public http(s) host; block loopback/private/reserved/
|
||||||
|
// link-local targets (incl. the cloud metadata endpoint).
|
||||||
|
$safeIp = SafeHttp::resolveSafeIp($url);
|
||||||
|
if (null === $safeIp) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$ch = curl_init($url);
|
$ch = curl_init($url);
|
||||||
curl_setopt($ch, CURLOPT_POST, true);
|
curl_setopt($ch, CURLOPT_POST, true);
|
||||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
|
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
|
||||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
// Forbid redirects/non-HTTP schemes and pin the validated IP
|
||||||
|
// to defeat DNS rebinding; keep TLS verification on.
|
||||||
|
curl_setopt_array($ch, SafeHttp::secureCurlOptions($url, $safeIp));
|
||||||
$data = curl_exec($ch);
|
$data = curl_exec($ch);
|
||||||
curl_close($ch);
|
curl_close($ch);
|
||||||
if($data === false) {
|
if($data === false) {
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
<?php
|
||||||
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Component\Essence;
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Http\SafeHttp;
|
||||||
|
use Essence\Http\Client;
|
||||||
|
use Essence\Http\Exception as EssenceHttpException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSRF-safe HTTP client for the Essence library.
|
||||||
|
*
|
||||||
|
* Essence fetches OEmbed/OpenGraph URLs server-side (Essence::_extract ->
|
||||||
|
* Client::get) while rendering the course/session "about" pages from a
|
||||||
|
* teacher-set video URL. Its bundled cURL/native clients have no private-IP
|
||||||
|
* filtering and follow redirects, so the fetch could reach loopback/internal
|
||||||
|
* hosts or the cloud metadata endpoint (CWE-918, SSRF).
|
||||||
|
*
|
||||||
|
* Injecting this client via
|
||||||
|
* `Essence\Essence::instance(["Http" => new SafeEssenceHttpClient()])` routes
|
||||||
|
* every Essence fetch through SafeHttp: it refuses loopback/private/reserved/
|
||||||
|
* link-local targets, speaks only http(s), forbids redirects and pins the
|
||||||
|
* validated IP (CURLOPT_RESOLVE) to defeat DNS rebinding.
|
||||||
|
*/
|
||||||
|
class SafeEssenceHttpClient extends Client
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function get($url)
|
||||||
|
{
|
||||||
|
$safeIp = SafeHttp::resolveSafeIp($url);
|
||||||
|
|
||||||
|
if (null === $safeIp) {
|
||||||
|
// Blocked or unresolvable target: surface Essence's own exception so
|
||||||
|
// the provider skips the embed instead of reaching internal content.
|
||||||
|
throw new EssenceHttpException($url, 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, false);
|
||||||
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
|
curl_setopt($ch, CURLOPT_USERAGENT, $this->_userAgent);
|
||||||
|
curl_setopt_array($ch, SafeHttp::secureCurlOptions($url, $safeIp));
|
||||||
|
|
||||||
|
$contents = curl_exec($ch);
|
||||||
|
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$failed = false === $contents || curl_errno($ch);
|
||||||
|
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
curl_close($ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($failed) {
|
||||||
|
throw new EssenceHttpException($url, $httpCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $contents;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ class RemoveOnAttributes extends HTMLPurifier_Filter
|
|||||||
|
|
||||||
public static function filter($html)
|
public static function filter($html)
|
||||||
{
|
{
|
||||||
|
// Strip null bytes before regex matching to prevent bypass via on\x00load patterns
|
||||||
|
$html = str_replace("\0", '', $html);
|
||||||
$pattern = '/\s+on\w+\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i';
|
$pattern = '/\s+on\w+\s*=\s*(?:"[^"]*"|\'[^\']*\'|[^\s>]+)/i';
|
||||||
|
|
||||||
return preg_replace($pattern, '', $html);
|
return preg_replace($pattern, '', $html);
|
||||||
|
|||||||
@@ -0,0 +1,152 @@
|
|||||||
|
<?php
|
||||||
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Component\Http;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared SSRF guard for every server-side fetch of a user-supplied URL.
|
||||||
|
*
|
||||||
|
* Chamilo issues outbound HTTP requests on behalf of users in several places
|
||||||
|
* (mPDF remote assets, the LTI cartridge auto-detect, ...). Without filtering,
|
||||||
|
* an attacker can point those fetches at loopback/private/reserved hosts or the
|
||||||
|
* cloud metadata endpoint (169.254.169.254) to scan the internal network or
|
||||||
|
* steal instance IAM credentials (CWE-918, SSRF).
|
||||||
|
*
|
||||||
|
* This class centralises the validation logic: resolve the host, reject any
|
||||||
|
* address inside a private/reserved/loopback/link-local range, and expose cURL
|
||||||
|
* options that pin the connection to the validated IP (defeating DNS rebinding)
|
||||||
|
* while forbidding non-HTTP schemes and redirects.
|
||||||
|
*
|
||||||
|
* The 1.11.x branch ships Symfony 3.4, which has no NoPrivateNetworkHttpClient,
|
||||||
|
* so the validation is implemented here directly.
|
||||||
|
*/
|
||||||
|
final class SafeHttp
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Validates the URL and returns a single safe IP to connect to, or null if
|
||||||
|
* the target must be blocked.
|
||||||
|
*
|
||||||
|
* A target is blocked when: the scheme is not http(s), the host is missing,
|
||||||
|
* the host does not resolve, or ANY resolved address falls inside a
|
||||||
|
* private/reserved/loopback/link-local range (e.g. 127.0.0.0/8,
|
||||||
|
* 169.254.0.0/16, 10/8, ::1, fc00::/7).
|
||||||
|
*
|
||||||
|
* @param string $uri
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function resolveSafeIp($uri)
|
||||||
|
{
|
||||||
|
$parts = parse_url($uri);
|
||||||
|
|
||||||
|
if (false === $parts || empty($parts["host"])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scheme = strtolower(isset($parts["scheme"]) ? $parts["scheme"] : "");
|
||||||
|
if (!in_array($scheme, ["http", "https"], true)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse_url keeps IPv6 literals wrapped in brackets.
|
||||||
|
$host = trim($parts["host"], "[]");
|
||||||
|
|
||||||
|
$addresses = self::resolveHost($host);
|
||||||
|
if (empty($addresses)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$safeIp = null;
|
||||||
|
foreach ($addresses as $address) {
|
||||||
|
if (!self::isPublicIp($address)) {
|
||||||
|
// A single private/reserved hit blocks the whole request.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (null === $safeIp) {
|
||||||
|
$safeIp = $address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $safeIp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the cURL options that harden an outbound request against SSRF:
|
||||||
|
* restrict the protocol to http(s), forbid following redirects (a 30x could
|
||||||
|
* bounce to an internal host after validation) and pin the connection to the
|
||||||
|
* already-validated IP to prevent a DNS-rebinding race.
|
||||||
|
*
|
||||||
|
* @param string $uri the validated URL
|
||||||
|
* @param string $ip the safe IP returned by resolveSafeIp()
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function secureCurlOptions($uri, $ip)
|
||||||
|
{
|
||||||
|
$parts = parse_url($uri);
|
||||||
|
$host = trim($parts["host"] ?? "", "[]");
|
||||||
|
$scheme = strtolower($parts["scheme"] ?? "http");
|
||||||
|
$port = (int) ($parts["port"] ?? ("https" === $scheme ? 443 : 80));
|
||||||
|
|
||||||
|
$options = [
|
||||||
|
CURLOPT_FOLLOWLOCATION => false,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => true,
|
||||||
|
CURLOPT_SSL_VERIFYHOST => 2,
|
||||||
|
CURLOPT_RESOLVE => ["$host:$port:$ip"],
|
||||||
|
];
|
||||||
|
|
||||||
|
if (defined("CURLOPT_PROTOCOLS") && defined("CURLPROTO_HTTP") && defined("CURLPROTO_HTTPS")) {
|
||||||
|
$options[CURLOPT_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
|
||||||
|
$options[CURLOPT_REDIR_PROTOCOLS] = CURLPROTO_HTTP | CURLPROTO_HTTPS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolves a host (or returns it as-is if already an IP literal) into a
|
||||||
|
* list of IPv4/IPv6 addresses.
|
||||||
|
*
|
||||||
|
* @param string $host
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
public static function resolveHost($host)
|
||||||
|
{
|
||||||
|
if (filter_var($host, FILTER_VALIDATE_IP)) {
|
||||||
|
return [$host];
|
||||||
|
}
|
||||||
|
|
||||||
|
$addresses = [];
|
||||||
|
|
||||||
|
$ipv4 = @gethostbynamel($host);
|
||||||
|
if (is_array($ipv4)) {
|
||||||
|
$addresses = $ipv4;
|
||||||
|
}
|
||||||
|
|
||||||
|
$records = @dns_get_record($host, DNS_AAAA);
|
||||||
|
if (is_array($records)) {
|
||||||
|
foreach ($records as $record) {
|
||||||
|
if (!empty($record["ipv6"])) {
|
||||||
|
$addresses[] = $record["ipv6"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($addresses));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $ip
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isPublicIp($ip)
|
||||||
|
{
|
||||||
|
return false !== filter_var(
|
||||||
|
$ip,
|
||||||
|
FILTER_VALIDATE_IP,
|
||||||
|
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
/* For licensing terms, see /license.txt */
|
||||||
|
|
||||||
|
namespace Chamilo\CoreBundle\Component\Mpdf;
|
||||||
|
|
||||||
|
use Chamilo\CoreBundle\Component\Http\SafeHttp;
|
||||||
|
use Mpdf\Container\SimpleContainer;
|
||||||
|
use Mpdf\Http\ClientInterface;
|
||||||
|
use Mpdf\PsrHttpMessageShim\Response;
|
||||||
|
use Mpdf\PsrHttpMessageShim\Stream;
|
||||||
|
use Psr\Http\Message\RequestInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSRF-safe HTTP client for mPDF.
|
||||||
|
*
|
||||||
|
* mPDF fetches every remote `<img src>` and CSS `url()` server-side while
|
||||||
|
* rendering. By default it uses its internal cURL/socket client with no IP
|
||||||
|
* filtering, which lets attacker-controlled wiki/document HTML reach the cloud
|
||||||
|
* metadata endpoint (169.254.169.254) or internal services (SSRF).
|
||||||
|
*
|
||||||
|
* This client is injected into mPDF through its experimental container (2nd
|
||||||
|
* constructor argument) and refuses any URL that resolves to a loopback,
|
||||||
|
* private, reserved or link-local address. The connection is pinned to the
|
||||||
|
* already-validated IP (CURLOPT_RESOLVE) and redirects are disabled so a remote
|
||||||
|
* 30x cannot bounce the request to an internal host.
|
||||||
|
*
|
||||||
|
* The 1.11.x branch ships Symfony 3.4, which has no NoPrivateNetworkHttpClient,
|
||||||
|
* so the validation is implemented here directly (the master branch relies on
|
||||||
|
* Symfony\Component\HttpClient\NoPrivateNetworkHttpClient instead).
|
||||||
|
*/
|
||||||
|
final class SafeMpdfHttpClient implements ClientInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Builds the mPDF container exposing this client as "httpClient".
|
||||||
|
*
|
||||||
|
* Usage: new \Mpdf\Mpdf($config, SafeMpdfHttpClient::container());
|
||||||
|
*/
|
||||||
|
public static function container(): SimpleContainer
|
||||||
|
{
|
||||||
|
return new SimpleContainer(["httpClient" => new self()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function sendRequest(RequestInterface $request)
|
||||||
|
{
|
||||||
|
$response = new Response();
|
||||||
|
|
||||||
|
$uri = (string) $request->getUri();
|
||||||
|
$ip = SafeHttp::resolveSafeIp($uri);
|
||||||
|
|
||||||
|
if (null === $ip) {
|
||||||
|
// Blocked or unresolvable target: return an empty 403 so mPDF
|
||||||
|
// silently skips the asset instead of embedding internal content.
|
||||||
|
return $response->withStatus(403);
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = parse_url($uri);
|
||||||
|
$host = $parts["host"];
|
||||||
|
$scheme = strtolower($parts["scheme"] ?? "http");
|
||||||
|
$port = (int) ($parts["port"] ?? ("https" === $scheme ? 443 : 80));
|
||||||
|
|
||||||
|
$ch = curl_init($uri);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, 0);
|
||||||
|
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
|
||||||
|
curl_setopt($ch, CURLOPT_TIMEOUT, 20);
|
||||||
|
// Never follow redirects: a 30x could point to an internal host that
|
||||||
|
// would bypass the validation performed above.
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
|
||||||
|
// Pin the connection to the IP we already validated to prevent a
|
||||||
|
// DNS-rebinding race between resolution and the actual request.
|
||||||
|
curl_setopt($ch, CURLOPT_RESOLVE, ["$host:$port:$ip"]);
|
||||||
|
|
||||||
|
$data = curl_exec($ch);
|
||||||
|
|
||||||
|
if (false === $data || curl_errno($ch)) {
|
||||||
|
self::closeCurl($ch);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
self::closeCurl($ch);
|
||||||
|
|
||||||
|
if ($httpCode < 200 || $httpCode >= 300) {
|
||||||
|
return $response->withStatus($httpCode ?: 502);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response
|
||||||
|
->withStatus($httpCode)
|
||||||
|
->withBody(Stream::create($data))
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function closeCurl($ch): void
|
||||||
|
{
|
||||||
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
curl_close($ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -288,6 +288,36 @@ class CourseRestorer
|
|||||||
CourseManager::update_attributes($origin_course_info['real_id'], $params);
|
CourseManager::update_attributes($origin_course_info['real_id'], $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates a relative path coming from an (untrusted) course backup
|
||||||
|
* archive before it is used in file operations.
|
||||||
|
*
|
||||||
|
* Rejects non-strings, path traversal (`..`) and null bytes, and optionally
|
||||||
|
* enforces that the path stays within an expected sub-directory. This
|
||||||
|
* prevents a tampered backup from escaping the course directory and writing
|
||||||
|
* an arbitrary file (ZIP-slip -> arbitrary file write -> RCE).
|
||||||
|
*
|
||||||
|
* @param mixed $path
|
||||||
|
* @param string $requiredPrefix Sub-tree the path must start with (optional)
|
||||||
|
*
|
||||||
|
* @return bool True when the path is safe to use
|
||||||
|
*/
|
||||||
|
private function isSafeBackupPath($path, $requiredPrefix = '')
|
||||||
|
{
|
||||||
|
if (!is_string($path)
|
||||||
|
|| false !== strpos($path, '..')
|
||||||
|
|| false !== strpos($path, "\0")
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('' !== $requiredPrefix && 0 !== strpos($path, $requiredPrefix)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore documents.
|
* Restore documents.
|
||||||
*
|
*
|
||||||
@@ -312,6 +342,14 @@ class CourseRestorer
|
|||||||
$path = api_get_path(SYS_COURSE_PATH).$this->course->destination_path.'/';
|
$path = api_get_path(SYS_COURSE_PATH).$this->course->destination_path.'/';
|
||||||
$originalFolderNameList = [];
|
$originalFolderNameList = [];
|
||||||
foreach ($resources[RESOURCE_DOCUMENT] as $id => $document) {
|
foreach ($resources[RESOURCE_DOCUMENT] as $id => $document) {
|
||||||
|
// Security: $document->path comes from the (possibly tampered)
|
||||||
|
// backup archive. Confine it to the "document" sub-tree and
|
||||||
|
// neutralize executable filenames to prevent arbitrary file write.
|
||||||
|
if (!$this->isSafeBackupPath($document->path, 'document')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$document->path = disable_dangerous_file($document->path);
|
||||||
|
|
||||||
$my_session_id = empty($document->item_properties[0]['session_id']) ? 0 : $session_id;
|
$my_session_id = empty($document->item_properties[0]['session_id']) ? 0 : $session_id;
|
||||||
|
|
||||||
if (false === $respect_base_content && $session_id) {
|
if (false === $respect_base_content && $session_id) {
|
||||||
@@ -1053,6 +1091,12 @@ class CourseRestorer
|
|||||||
if ($this->course->has_resources(RESOURCE_SCORM)) {
|
if ($this->course->has_resources(RESOURCE_SCORM)) {
|
||||||
$resources = $this->course->resources;
|
$resources = $this->course->resources;
|
||||||
foreach ($resources[RESOURCE_SCORM] as $document) {
|
foreach ($resources[RESOURCE_SCORM] as $document) {
|
||||||
|
// Security: confine the backup-supplied SCORM path to prevent a
|
||||||
|
// tampered backup from escaping the course directory (-> RCE).
|
||||||
|
if (!$this->isSafeBackupPath($document->path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$path = api_get_path(SYS_COURSE_PATH).$this->course->destination_path.'/';
|
$path = api_get_path(SYS_COURSE_PATH).$this->course->destination_path.'/';
|
||||||
@mkdir(dirname($path.$document->path), $perm, true);
|
@mkdir(dirname($path.$document->path), $perm, true);
|
||||||
if (file_exists($path.$document->path)) {
|
if (file_exists($path.$document->path)) {
|
||||||
|
|||||||
Vendored
+12
-12
@@ -57,7 +57,7 @@ if (!isset($_SERVER['REQUEST_URI']) && isset($_SERVER['SCRIPT_NAME']) && isset($
|
|||||||
/**
|
/**
|
||||||
* phpCAS version. accessible for the user by phpCAS::getVersion().
|
* phpCAS version. accessible for the user by phpCAS::getVersion().
|
||||||
*/
|
*/
|
||||||
define('PHPCAS_VERSION', '1.6.1');
|
define('PHPCAS_VERSION', '1.6.2');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @addtogroup public
|
* @addtogroup public
|
||||||
@@ -303,7 +303,7 @@ class phpCAS
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* This variable is used to enable verbose mode
|
* This variable is used to enable verbose mode
|
||||||
* This pevents debug info to be show to the user. Since it's a security
|
* This prevents debug info to be show to the user. Since it's a security
|
||||||
* feature the default is false
|
* feature the default is false
|
||||||
*
|
*
|
||||||
* @hideinitializer
|
* @hideinitializer
|
||||||
@@ -338,7 +338,7 @@ class phpCAS
|
|||||||
* @param bool $changeSessionID Allow phpCAS to change the session_id
|
* @param bool $changeSessionID Allow phpCAS to change the session_id
|
||||||
* (Single Sign Out/handleLogoutRequests
|
* (Single Sign Out/handleLogoutRequests
|
||||||
* is based on that change)
|
* is based on that change)
|
||||||
* @param \SessionHandlerInterface $sessionHandler the session handler
|
* @param \SessionHandlerInterface|null $sessionHandler the session handler
|
||||||
*
|
*
|
||||||
* @return void a newly created CAS_Client object
|
* @return void a newly created CAS_Client object
|
||||||
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
|
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
|
||||||
@@ -347,7 +347,7 @@ class phpCAS
|
|||||||
*/
|
*/
|
||||||
public static function client($server_version, $server_hostname,
|
public static function client($server_version, $server_hostname,
|
||||||
$server_port, $server_uri, $service_base_url,
|
$server_port, $server_uri, $service_base_url,
|
||||||
$changeSessionID = true, \SessionHandlerInterface $sessionHandler = null
|
$changeSessionID = true, ?\SessionHandlerInterface $sessionHandler = null
|
||||||
) {
|
) {
|
||||||
phpCAS :: traceBegin();
|
phpCAS :: traceBegin();
|
||||||
if (is_object(self::$_PHPCAS_CLIENT)) {
|
if (is_object(self::$_PHPCAS_CLIENT)) {
|
||||||
@@ -393,7 +393,7 @@ class phpCAS
|
|||||||
* @param bool $changeSessionID Allow phpCAS to change the session_id
|
* @param bool $changeSessionID Allow phpCAS to change the session_id
|
||||||
* (Single Sign Out/handleLogoutRequests
|
* (Single Sign Out/handleLogoutRequests
|
||||||
* is based on that change)
|
* is based on that change)
|
||||||
* @param \SessionHandlerInterface $sessionHandler the session handler
|
* @param \SessionHandlerInterface|null $sessionHandler the session handler
|
||||||
*
|
*
|
||||||
* @return void a newly created CAS_Client object
|
* @return void a newly created CAS_Client object
|
||||||
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
|
* @note Only one of the phpCAS::client() and phpCAS::proxy functions should be
|
||||||
@@ -402,14 +402,14 @@ class phpCAS
|
|||||||
*/
|
*/
|
||||||
public static function proxy($server_version, $server_hostname,
|
public static function proxy($server_version, $server_hostname,
|
||||||
$server_port, $server_uri, $service_base_url,
|
$server_port, $server_uri, $service_base_url,
|
||||||
$changeSessionID = true, \SessionHandlerInterface $sessionHandler = null
|
$changeSessionID = true, ?\SessionHandlerInterface $sessionHandler = null
|
||||||
) {
|
) {
|
||||||
phpCAS :: traceBegin();
|
phpCAS :: traceBegin();
|
||||||
if (is_object(self::$_PHPCAS_CLIENT)) {
|
if (is_object(self::$_PHPCAS_CLIENT)) {
|
||||||
phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')');
|
phpCAS :: error(self::$_PHPCAS_INIT_CALL['method'] . '() has already been called (at ' . self::$_PHPCAS_INIT_CALL['file'] . ':' . self::$_PHPCAS_INIT_CALL['line'] . ')');
|
||||||
}
|
}
|
||||||
|
|
||||||
// store where the initialzer is called from
|
// store where the initializer is called from
|
||||||
$dbg = debug_backtrace();
|
$dbg = debug_backtrace();
|
||||||
self::$_PHPCAS_INIT_CALL = array (
|
self::$_PHPCAS_INIT_CALL = array (
|
||||||
'done' => true,
|
'done' => true,
|
||||||
@@ -560,7 +560,7 @@ class phpCAS
|
|||||||
|
|
||||||
$indent_str .= '| ';
|
$indent_str .= '| ';
|
||||||
}
|
}
|
||||||
// allow for multiline output with proper identing. Usefull for
|
// allow for multiline output with proper identing. Useful for
|
||||||
// dumping cas answers etc.
|
// dumping cas answers etc.
|
||||||
$str2 = str_replace("\n", "\n" . self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str, $str);
|
$str2 = str_replace("\n", "\n" . self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str, $str);
|
||||||
$str3 = self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2;
|
$str3 = self::$_PHPCAS_DEBUG['unique_id'] . ' ' . $indent_str . $str2;
|
||||||
@@ -568,7 +568,7 @@ class phpCAS
|
|||||||
self::$_PHPCAS_DEBUG['logger']->info($str3);
|
self::$_PHPCAS_DEBUG['logger']->info($str3);
|
||||||
}
|
}
|
||||||
if (!empty(self::$_PHPCAS_DEBUG['filename'])) {
|
if (!empty(self::$_PHPCAS_DEBUG['filename'])) {
|
||||||
// Check if file exists and modifiy file permissions to be only
|
// Check if file exists and modify file permissions to be only
|
||||||
// readable by the webserver
|
// readable by the webserver
|
||||||
if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) {
|
if (!file_exists(self::$_PHPCAS_DEBUG['filename'])) {
|
||||||
touch(self::$_PHPCAS_DEBUG['filename']);
|
touch(self::$_PHPCAS_DEBUG['filename']);
|
||||||
@@ -1769,7 +1769,7 @@ class phpCAS
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* If you want your service to be proxied you have to enable it (default
|
* If you want your service to be proxied you have to enable it (default
|
||||||
* disabled) and define an accepable list of proxies that are allowed to
|
* disabled) and define an acceptable list of proxies that are allowed to
|
||||||
* proxy your service.
|
* proxy your service.
|
||||||
*
|
*
|
||||||
* Add each allowed proxy definition object. For the normal CAS_ProxyChain
|
* Add each allowed proxy definition object. For the normal CAS_ProxyChain
|
||||||
@@ -1790,7 +1790,7 @@ class phpCAS
|
|||||||
* 'http://client.example.com/'
|
* 'http://client.example.com/'
|
||||||
* )));
|
* )));
|
||||||
*
|
*
|
||||||
* For quick testing or in certain production screnarios you might want to
|
* For quick testing or in certain production scenarios you might want to
|
||||||
* allow allow any other valid service to proxy your service. To do so, add
|
* allow allow any other valid service to proxy your service. To do so, add
|
||||||
* the "Any" chain:
|
* the "Any" chain:
|
||||||
* phpCAS::allowProxyChain(new CAS_ProxyChain_Any);
|
* phpCAS::allowProxyChain(new CAS_ProxyChain_Any);
|
||||||
@@ -1897,7 +1897,7 @@ class phpCAS
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks of a proxy client aready exists
|
* Checks of a proxy client already exists
|
||||||
*
|
*
|
||||||
* @throws CAS_OutOfSequenceBeforeProxyException
|
* @throws CAS_OutOfSequenceBeforeProxyException
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ implements CAS_Exception
|
|||||||
* authenticated.
|
* authenticated.
|
||||||
*
|
*
|
||||||
* @param CAS_Client $client phpcas client
|
* @param CAS_Client $client phpcas client
|
||||||
* @param string $failure the failure that occured
|
* @param string $failure the failure that occurred
|
||||||
* @param string $cas_url the URL the CAS server was asked for
|
* @param string $cas_url the URL the CAS server was asked for
|
||||||
* @param bool $no_response the response from the CAS server (other
|
* @param bool $no_response the response from the CAS server (other
|
||||||
* parameters are ignored if TRUE)
|
* parameters are ignored if TRUE)
|
||||||
|
|||||||
+5
-5
@@ -788,7 +788,7 @@ class CAS_Client
|
|||||||
'file' => $dbg[1]['file'],
|
'file' => $dbg[1]['file'],
|
||||||
'line' => $dbg[1]['line'],
|
'line' => $dbg[1]['line'],
|
||||||
'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'],
|
'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'],
|
||||||
'result' => (boolean)$auth
|
'result' => (bool)$auth
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
private $_authentication_caller;
|
private $_authentication_caller;
|
||||||
@@ -926,7 +926,7 @@ class CAS_Client
|
|||||||
* CAS_ServiceBaseUrl_Interface for custom
|
* CAS_ServiceBaseUrl_Interface for custom
|
||||||
* behavior. Added in 1.6.0. Similar to
|
* behavior. Added in 1.6.0. Similar to
|
||||||
* serverName config in other CAS clients.
|
* serverName config in other CAS clients.
|
||||||
* @param \SessionHandlerInterface $sessionHandler the session handler
|
* @param \SessionHandlerInterface|null $sessionHandler the session handler
|
||||||
*
|
*
|
||||||
* @return self a newly created CAS_Client object
|
* @return self a newly created CAS_Client object
|
||||||
*/
|
*/
|
||||||
@@ -938,7 +938,7 @@ class CAS_Client
|
|||||||
$server_uri,
|
$server_uri,
|
||||||
$service_base_url,
|
$service_base_url,
|
||||||
$changeSessionID = true,
|
$changeSessionID = true,
|
||||||
\SessionHandlerInterface $sessionHandler = null
|
?\SessionHandlerInterface $sessionHandler = null
|
||||||
) {
|
) {
|
||||||
// Argument validation
|
// Argument validation
|
||||||
if (gettype($server_version) != 'string')
|
if (gettype($server_version) != 'string')
|
||||||
@@ -3166,7 +3166,7 @@ class CAS_Client
|
|||||||
$proxiedService->setCasClient($this);
|
$proxiedService->setCasClient($this);
|
||||||
}
|
}
|
||||||
return $proxiedService;
|
return $proxiedService;
|
||||||
case PHPCAS_PROXIED_SERVICE_IMAP;
|
case PHPCAS_PROXIED_SERVICE_IMAP:
|
||||||
$proxiedService = new CAS_ProxiedService_Imap($this->_getUser());
|
$proxiedService = new CAS_ProxiedService_Imap($this->_getUser());
|
||||||
if ($proxiedService instanceof CAS_ProxiedService_Testable) {
|
if ($proxiedService instanceof CAS_ProxiedService_Testable) {
|
||||||
$proxiedService->setCasClient($this);
|
$proxiedService->setCasClient($this);
|
||||||
@@ -4163,7 +4163,7 @@ class CAS_Client
|
|||||||
* This method is used to print the HTML output when the user was not
|
* This method is used to print the HTML output when the user was not
|
||||||
* authenticated.
|
* authenticated.
|
||||||
*
|
*
|
||||||
* @param string $failure the failure that occured
|
* @param string $failure the failure that occurred
|
||||||
* @param string $cas_url the URL the CAS server was asked for
|
* @param string $cas_url the URL the CAS server was asked for
|
||||||
* @param bool $no_response the response from the CAS server (other
|
* @param bool $no_response the response from the CAS server (other
|
||||||
* parameters are ignored if true)
|
* parameters are ignored if true)
|
||||||
|
|||||||
+1
-1
@@ -316,7 +316,7 @@ class CAS_PGTStorage_Db extends CAS_PGTStorage_AbstractStorage
|
|||||||
try {
|
try {
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
$query = $pdo->query($this->createTableSQL());
|
$query = $pdo->query($this->createTableSql());
|
||||||
$query->closeCursor();
|
$query->closeCursor();
|
||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|||||||
@@ -128,6 +128,12 @@ class CAS_PGTStorage_File extends CAS_PGTStorage_AbstractStorage
|
|||||||
phpCAS::error('an absolute path is needed for PGT storage to file');
|
phpCAS::error('an absolute path is needed for PGT storage to file');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure that the directory separator on Windows is '/' for consistency with the rest of the phpcas code
|
||||||
|
$path = str_replace(DIRECTORY_SEPARATOR , '/', $path);
|
||||||
|
|
||||||
|
// store the path (with a trailing '/')
|
||||||
|
$path = preg_replace('|([^/])$|', '$1/', $path);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if ( $path[0] != '/' ) {
|
if ( $path[0] != '/' ) {
|
||||||
|
|||||||
@@ -139,7 +139,12 @@ implements CAS_Request_MultiRequestInterface
|
|||||||
$buf = curl_multi_getcontent($handles[$i]);
|
$buf = curl_multi_getcontent($handles[$i]);
|
||||||
$request->_storeResponseBody($buf);
|
$request->_storeResponseBody($buf);
|
||||||
curl_multi_remove_handle($multiHandle, $handles[$i]);
|
curl_multi_remove_handle($multiHandle, $handles[$i]);
|
||||||
curl_close($handles[$i]);
|
if (PHP_VERSION_ID < 80000) {
|
||||||
|
curl_close($handles[$i]);
|
||||||
|
} else {
|
||||||
|
// unreference it => it will be closed
|
||||||
|
unset($handles[$i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
curl_multi_close($multiHandle);
|
curl_multi_close($multiHandle);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user