Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
274 commits
Select commit Hold shift + click to select a range
99df7ff
wpcs issues fix
harunollyo Sep 30, 2026
2000b62
fix: prevent SQL injection via unescaped column names in prepare_set_…
harunollyo Sep 30, 2026
abbefd6
fix: prevent SQL injection via
harunollyo Sep 30, 2026
41ffaf6
refactor(restapi): remove SSL requirement checks from authentication …
saadman30 Sep 30, 2026
90e0054
fix: prevent SQL injection via array keys in prepare_like_clause()
harunollyo Sep 30, 2026
4cc6c28
feat: add quote_sql_identifier method to safely quote SQL identifiers
harunollyo Sep 30, 2026
3994fc0
fix: wrap table names in backticks in QueryHelper SQL queries
harunollyo Sep 30, 2026
a427599
docs: add blank line to quote_sql_identifier docblock
harunollyo Oct 1, 2026
9a4ee06
fix: use quote_sql_identifier for where_col in QueryHelper
harunollyo Oct 1, 2026
a47077a
refactor: improve SQL identifier validation and quoting in QueryHelper
harunollyo Oct 1, 2026
5eae3ea
fix: harden SQL query generation against injection by validating and …
harunollyo Oct 1, 2026
ff56865
guzzle removed and made necessary changes for it
sanjana4khan Oct 1, 2026
be63789
feat: added chainable fluent QueryBuilder helper and unit test
harunollyo Oct 1, 2026
9647b74
chore: update repository files
harunollyo Oct 1, 2026
52f355b
chore: update @since version tags to 4.1.2
harunollyo Oct 1, 2026
689ace1
Paypal fatal error fix for recurring payment.
sanjana4khan Oct 1, 2026
9420da1
Version tag added
sanjana4khan Oct 2, 2026
02d3304
developer doc added for DB helper
harunollyo Oct 2, 2026
afc91e1
removed new request and response class and used HttpHelper::class
sanjana4khan Oct 2, 2026
7fd4f3b
fix(quiz): Verify answers server-side in reveal mode to prevent answe…
sazedul-haque Oct 2, 2026
8c1c119
Used httphelper for paypal
sanjana4khan Oct 2, 2026
f33ae75
Extra Meta data added for payment gateway
sanjana4khan Oct 4, 2026
4237461
condition added for invalid order id.
sanjana4khan Oct 4, 2026
f98c9ce
Put and Patch function added
sanjana4khan Oct 5, 2026
c4a9b0e
fix(course-builder): update quiz negative marking settings and mismat…
b-l-i-n-d Oct 5, 2026
3c52a94
Simplified req and response
sanjana4khan Oct 5, 2026
4dda691
refactor(restapi): update permission callbacks for enhanced access co…
saadman30 Oct 5, 2026
fe16c20
Merge branch '4.1.1' into fix/rest-idor-issues
saadman30 Oct 5, 2026
7d3e870
Merge branch 'dev' of https://github.com/themeum/tutor into harun
harunollyo Oct 5, 2026
56fd501
Warning message fix
sanjana4khan Oct 5, 2026
bdb12d4
Add full support for v4 design system in registration forms
sazedul-haque Oct 5, 2026
699a939
docs(quiz): Update input label
b-l-i-n-d Oct 5, 2026
cdbda25
version tag added
sanjana4khan Oct 5, 2026
a65cd07
Enhance API permission handling in RestAuth
saadman30 Oct 5, 2026
f74973a
Refactor permission handling in RestAuth class
saadman30 Oct 5, 2026
5458c8f
$this->json_response used instead of wp_send_json
sazedul-haque Oct 5, 2026
b2485d4
Unnecessary authentication check removed
sazedul-haque Oct 5, 2026
33f9d9a
Merge branch 'dev' into fix-quiz-answer-reveal
sazedul-haque Oct 5, 2026
a11e152
Fix quiz attempt JS escaping
sazedul-haque Oct 5, 2026
0642293
[Security] GDPR security issue fixes
Anindra123 Oct 6, 2026
52e0e20
Improve error handling in RestAuth class
saadman30 Oct 6, 2026
4c89cf7
Fix: Added csv injection sanitization method
Anindra123 Oct 6, 2026
f0eb850
Merge branch 'dev' of https://github.com/themeum/tutor into harun
harunollyo Oct 6, 2026
7c0cfb8
misc/fixes
Anindra123 Oct 6, 2026
0ffb6bf
Made separate class for Http request and helper function.
sanjana4khan Oct 6, 2026
ed8dad5
Removed unused code from paypal
sanjana4khan Oct 6, 2026
efe0ea8
Refactor REST API response handling across multiple classes
saadman30 Oct 6, 2026
3004656
fix(quiz): Persist verified answers and restore reveal state on reload
sazedul-haque Oct 6, 2026
5bddc2d
Merge branch 'dev' into fix/rest-idor-issues
saadman30 Oct 6, 2026
9070afb
fix(quiz): Allow skipping reveal wait timer on user navigation in leg…
sazedul-haque Oct 6, 2026
7b5ae91
fix(quiz): Add loading spinner to buttons during answer verification …
sazedul-haque Oct 6, 2026
4be0f14
Resolve warning message
sanjana4khan Oct 6, 2026
cc717fa
Merge branch 'dev' into remove-guzzle
sanjana4khan Oct 6, 2026
cf928b2
refactor(quiz): Remove empty div
b-l-i-n-d Oct 7, 2026
f23af0b
Fix quiz submit timing and validation
sazedul-haque Oct 7, 2026
917e0cf
Again added env parameter to prepareMerchantMetadata() and made the …
sanjana4khan Oct 7, 2026
4f11914
style(quiz): Update inline form styles
b-l-i-n-d Oct 7, 2026
b9262a6
feat(quiz): add countdown fill animation to navigation buttons during…
sazedul-haque Oct 7, 2026
31bff5a
Unit test added for HttpHelper::class
sanjana4khan Oct 7, 2026
4130af8
Version updated
sanjana4khan Oct 7, 2026
6d70438
docs: add execution command instruction to HttpHelperTest docblock
harunollyo Oct 7, 2026
44b10a6
Rename quiz reveal result attribute
sazedul-haque Oct 7, 2026
3e64a54
fix(settings): confirm penalty unit change with dynamic message
b-l-i-n-d Oct 7, 2026
51e1bc3
made exception folder outside of Paypal
sanjana4khan Oct 7, 2026
76c096d
added missing comments
sanjana4khan Oct 7, 2026
0383419
Remove reduce motion support from legacy mode
sazedul-haque Oct 7, 2026
8b865e2
Fix back button does not update pagination active state
sazedul-haque Oct 7, 2026
ede5b42
Update FileUploader.php
sazedul-haque Oct 7, 2026
6af1d02
Update API documentation to reflect JWT authentication changes
saadman30 Oct 8, 2026
082d10a
Enhance quiz answer visibility control in REST API
saadman30 Oct 8, 2026
41de15c
Merge branch '4.2.0' into security/gdpr
Anindra123 Oct 8, 2026
00f9ff9
Merge pull request #3054 from themeum/remove-guzzle
harunollyo Oct 8, 2026
64f16ca
Format Quiz.php
sazedul-haque Oct 8, 2026
36786fa
Fix reduce motion settings.
sazedul-haque Oct 8, 2026
4389558
Merge branch 'dev' into fix-quiz-answer-reveal
sazedul-haque Oct 8, 2026
0259c5e
Merge remote-tracking branch 'origin/4.2.0' into fix-quiz-answer-reveal
sazedul-haque Oct 8, 2026
7b542bf
updated xss fix and csv security fix in js files only
Anindra123 Oct 8, 2026
18e5e09
Merge remote-tracking branch 'origin/4.2.0' into fix-registration-forms
sazedul-haque Oct 8, 2026
2e310ef
refactor: update course and subscription purchase validation logic in…
harunollyo Oct 8, 2026
ba4252d
fix: add phpcs ignore rule for legal consent validation in checkout c…
harunollyo Oct 8, 2026
1c4be47
fix: cs warning
harunollyo Oct 8, 2026
5cf9574
Merge pull request #3073 from themeum/z8zyy1fyxa
shewa12 Oct 8, 2026
b7dac10
private course issue fix
sanjana4khan Oct 8, 2026
600f802
fix(quiz): Resolve manual override button not color graded
b-l-i-n-d Oct 8, 2026
d2aeb5b
fix(quiz): prevent attempt status lockout on skipped essay questions
b-l-i-n-d Oct 8, 2026
ece78e9
Version & changelog updated
shewa12 Oct 9, 2026
6a022cb
Update course pricing retrieval in REST API
saadman30 Oct 9, 2026
7ae8723
refactor: update DB helper and test suite versions to 4.2.0
harunollyo Oct 9, 2026
181cc19
fix(settings): revert save button state on toggle turnoff cancellation
b-l-i-n-d Oct 9, 2026
407d67a
refactor(settings): simplify toggle save button state capture
b-l-i-n-d Oct 9, 2026
a285a74
fix: strip quotes and whitespace from array values in QueryHelper pre…
harunollyo Oct 9, 2026
a304011
fix(dashboard): sync student quick tips with site configuration
b-l-i-n-d Oct 9, 2026
998dd63
Optimization: Optimized the get_course_meta method from Utils class
shewa12 Oct 9, 2026
b0e8676
Merge pull request #3071 from themeum/fix/quiz-negative-mark-type-con…
b-l-i-n-d Oct 9, 2026
1c71638
Refactor reveal timeout and submit flow
sazedul-haque Oct 9, 2026
5210ff9
Restrict instant answer verification
sazedul-haque Oct 9, 2026
b27385a
Update API documentation for authentication permissions
saadman30 Oct 9, 2026
4d235c6
Update API documentation for permission details
saadman30 Oct 9, 2026
52e3aa1
Ensure skipped questions are recorded in attempt answers
sazedul-haque Oct 9, 2026
1d8ea73
Update API documentation for course and quiz endpoints
saadman30 Oct 9, 2026
7293e3d
Remove pending-review gating from attempts
sazedul-haque Oct 9, 2026
e7bab45
Fix(🛠️): Guest user can purchase previously carted private course
sanjana4khan Oct 11, 2026
eb32816
Merge branch '4.2.0' into cart-folder-issues
sanjana4khan Oct 11, 2026
6fdffc2
is used without being initialized in do_enroll()
sanjana4khan Oct 11, 2026
6ad10e6
fix(dashboard): elevate header stacking context when mobile user draw…
b-l-i-n-d Oct 12, 2026
5aa7052
Merge branch 'dev' of https://github.com/themeum/tutor into shewa
shewa12 Oct 12, 2026
1fc1e9b
is_course_accessible() noe can accept both wp object or course id.
sanjana4khan Oct 12, 2026
c0e78ce
fix(templates): enforce logged-in user profile in account route
b-l-i-n-d Oct 12, 2026
c4287bd
Refactor REST API response codes to use HttpHelper constants
saadman30 Oct 12, 2026
0a2e824
error fix for string id in is_course_accessible()
sanjana4khan Oct 12, 2026
dab1b45
Release date updated
shewa12 Oct 12, 2026
e0c3f10
Stop tracking Bruno Tutorlms environment file.
saadman30 Oct 12, 2026
b9d2bb3
Refactor security checks in REST API classes
saadman30 Oct 12, 2026
dc892c7
Merge branch 'dev' of https://github.com/themeum/tutor into shewa
shewa12 Oct 12, 2026
b86380a
Comment added
sanjana4khan Oct 12, 2026
43285ad
Add get_namespace method to RestAPI class
saadman30 Oct 12, 2026
ed29a59
Refactor RestAPI class to use static namespace
saadman30 Oct 12, 2026
a1a4d37
Refactor REST API methods to utilize HttpHelper constants
saadman30 Oct 12, 2026
74d8aea
fix(settings): prevent image upload preview height collapsing on smal…
b-l-i-n-d Oct 12, 2026
f1fcc2a
Refactor REST API authentication methods for consistency
saadman30 Oct 12, 2026
1b632ff
Update REST API permission handling for public access
saadman30 Oct 12, 2026
025c1ec
Refactor course order handling in REST API
saadman30 Oct 12, 2026
a5ae851
Update type hinting in REST_Lesson class
saadman30 Oct 12, 2026
15af0cd
Refactor quiz table references in REST_Quiz class
saadman30 Oct 12, 2026
a8b615f
Revert changes for composer.lock
sanjana4khan Oct 12, 2026
d4f1206
Update quiz ID sanitization in REST_Quiz class
saadman30 Oct 12, 2026
b16d342
Update REST API permission handling for course ratings
saadman30 Oct 12, 2026
9a24d80
Refactor access token lifetime constants in RestAuth class
saadman30 Oct 12, 2026
bc69e93
used empty array check by using tutils()->count
sanjana4khan Oct 13, 2026
0829fc6
Enhance API key management error handling and response structure
saadman30 Oct 13, 2026
c1262e4
Enhance course ID resolution in RestAuth class
saadman30 Oct 13, 2026
c3b0605
Refactor user ID retrieval in RestAuth class
saadman30 Oct 13, 2026
61bb969
Merge pull request #3063 from themeum/security/gdpr
shewa12 Oct 13, 2026
6a4c1a2
Merge pull request #3079 from themeum/shewa
harunollyo Oct 13, 2026
447f017
Merge pull request #3046 from themeum/harun
shewa12 Oct 13, 2026
4512eee
Merge pull request #3077 from themeum/fix-mobile-dashboard-logout-button
b-l-i-n-d Oct 13, 2026
457a01b
Remove redundant API documentation comment in RestAuth class
saadman30 Oct 13, 2026
a306dac
Merge pull request #3080 from themeum/fix-image-upload-preview-height
b-l-i-n-d Oct 13, 2026
0c02db6
Enhance RestAuth class constructor to support optional hook registration
saadman30 Oct 13, 2026
b76ba06
Add base64 encoding/decoding fallback in RestAuth class
saadman30 Oct 13, 2026
2c503cb
Refactor REST_Response error handling logic
saadman30 Oct 13, 2026
e434b3b
Update logout functionality in RestAuth class
saadman30 Oct 13, 2026
ba6a298
Enhance REST API course access and metadata handling
saadman30 Oct 13, 2026
d720172
Remove ignored TutorLMS environment files from .gitignore
saadman30 Oct 13, 2026
660654a
refactor: rename table_expr variable to table_name in database update…
harunollyo Oct 13, 2026
55d6d2d
Merge pull request #3081 from themeum/cart-folder-issues
shewa12 Oct 13, 2026
d6e66e7
Merge pull request #3078 from themeum/fix-user-profile-account-route-id
shewa12 Oct 13, 2026
1503a1f
Merge pull request #3061 from themeum/fix-registration-forms
shewa12 Oct 13, 2026
a4cbc8b
fix: prevent array_keys error in DB helper when first query result ro…
harunollyo Oct 13, 2026
f1a15d3
refactor: simplify total count calculation by removing redundant reco…
harunollyo Oct 13, 2026
09f490b
warning message in checkout
sanjana4khan Oct 13, 2026
bd6071f
feat: Add export analytics endpoint
b-l-i-n-d Oct 13, 2026
52ec836
feat: introduce DatabaseException and replace generic exceptions in D…
harunollyo Oct 13, 2026
009557c
feat: add support for column aliases and closure-based multiple ON co…
harunollyo Oct 13, 2026
4ac30e2
Merge branch '4.2.0' of https://github.com/themeum/tutor into query-b…
harunollyo Oct 13, 2026
6e2cc97
Merge pull request #3057 from themeum/fix-quiz-answer-reveal
shewa12 Oct 14, 2026
e5baf46
refactor: add bootstrap loader with legacy class namespace mapping an…
harunollyo Oct 14, 2026
e2207bc
refactor: update class mapping documentation in bootstrap.php
harunollyo Oct 14, 2026
608e86a
Update API documentation for authentication and response structures
saadman30 Oct 14, 2026
78eaae0
Merge pull request #3085 from themeum/warning-message-checkout
harunollyo Oct 14, 2026
a83f98e
refactor: update Input class namespace across core, ecommerce, and te…
harunollyo Oct 14, 2026
0a4fde3
Merge pull request #3075 from themeum/fix/dashboard-quick-tips-config
harunollyo Oct 14, 2026
7fedc49
Merge pull request #3059 from themeum/fix/quiz-partial
b-l-i-n-d Oct 14, 2026
94bc185
Revert "warning message in checkout"
harunollyo Oct 14, 2026
71bcc4c
Merge pull request #3090 from themeum/revert-3085-warning-message-che…
harunollyo Oct 14, 2026
8c53b83
Revert "Guest Cart and checkout Issues"
harunollyo Oct 14, 2026
c1775c8
Merge pull request #3091 from themeum/revert-3081-cart-folder-issues
harunollyo Oct 14, 2026
961aa98
Merge branch '4.2.0' into fix/quiz-status
b-l-i-n-d Oct 14, 2026
1339cd0
Fix checkout null handling
sazedul-haque Oct 14, 2026
921e1f0
Merge pull request #3088 from themeum/core-namespace
shewa12 Oct 14, 2026
3a737c9
refactor(quiz): Remove unnecessary code
b-l-i-n-d Oct 14, 2026
595d459
fix: improve database query builder join checks, distinct count handl…
harunollyo Oct 14, 2026
8683892
Merge branch '4.2.0' of https://github.com/themeum/tutor into query-b…
harunollyo Oct 14, 2026
d27d708
fix: update DB class import path in DBTestphpunit tests
harunollyo Oct 14, 2026
1644faa
revert changes
harunollyo Oct 14, 2026
6c2b6c6
Update _billing.scss
sazedul-haque Oct 14, 2026
aad990a
Fix translation
sazedul-haque Oct 14, 2026
026672e
Enhance course access control in RestAPI class
saadman30 Oct 14, 2026
1587399
Merge pull request #3074 from themeum/fix/quiz-status
harunollyo Oct 14, 2026
4b125c7
Merge branch '4.2.0' into fix/rest-idor-issues
saadman30 Oct 15, 2026
816b53f
`QueryHelper::prepare_raw_query()` started returning an empty SQL que…
sanjana4khan Oct 15, 2026
abb4053
Merge pull request #3094 from themeum/fatal_error_instructor_dashboard
harunollyo Oct 15, 2026
886f38b
Merge pull request #3052 from themeum/query-builder
shewa12 Oct 15, 2026
78a23b1
PR description template added
shewa12 Oct 15, 2026
92bd799
Version & changelog updated
shewa12 Oct 15, 2026
6b0589c
Revert "Version & changelog updated"
shewa12 Oct 15, 2026
0d74066
Workflow added for PR description
shewa12 Oct 15, 2026
df3022c
refactor(restapi): Remove deprecated object-level access checks
saadman30 Oct 15, 2026
b7fb7e7
Merge pull request #3092 from themeum/fix-checkout-console-error
harunollyo Oct 15, 2026
936ef79
Refactor consent logs modal and CSV safety
sazedul-haque Oct 15, 2026
6523846
Correct @since version annotations
sazedul-haque Oct 15, 2026
f5d0404
Remove CSV sanitization from consent export
sazedul-haque Oct 15, 2026
bb1512e
Fix legal consent validation response
sazedul-haque Oct 15, 2026
31e6ecb
fix(CourseModel): bind placeholder in quiz answer delete query
saadman30 Oct 15, 2026
acc444d
Sanitize consent log CSV names
sazedul-haque Oct 15, 2026
8ee1b26
Merge pull request #3026 from themeum/fix/rest-idor-issues
shewa12 Oct 15, 2026
e48491b
Improve consent logs formatting
sazedul-haque Oct 15, 2026
82470ee
Use shared AJAX helpers for consent logs
sazedul-haque Oct 15, 2026
ce79249
Version & changelog updated
shewa12 Oct 15, 2026
398486d
Merge pull request #3098 from themeum/refactor-consent-log-dev
shewa12 Oct 15, 2026
31a0775
Merge branch 'dev' of https://github.com/themeum/tutor into dev
shewa12 Oct 15, 2026
2d17d1e
Version & changelog updated
shewa12 Oct 15, 2026
efad68e
Merge branch '4.2.0' of https://github.com/themeum/tutor into 4.2.0
shewa12 Oct 15, 2026
b75fde3
feat(api-keys): enhance API key management UI and functionality
saadman30 Oct 16, 2026
db6a93d
feat(api): enhance error handling and resource validation in REST API
saadman30 Oct 16, 2026
f3d70f8
Merge pull request #3086 from themeum/feat/async-analytics-report
b-l-i-n-d Oct 16, 2026
bdd4c95
feat(security): add sanitizeCsvCell utility function
b-l-i-n-d Oct 16, 2026
b162e70
Merge branch '4.2.0' into feat/new-secret-key-policy
saadman30 Oct 16, 2026
94dff22
Merge pull request #3102 from themeum/feat/async-analytics-report
b-l-i-n-d Oct 16, 2026
a24e9c8
refactor(api-keys): streamline API key management functionality
saadman30 Oct 16, 2026
ced34cf
refactor(popupMenu): simplify clipboard interaction logic
saadman30 Oct 16, 2026
845050e
Coupon: Updated coupon table & prevented race condition issue
shewa12 Oct 19, 2026
a45e416
Merge pull request #3101 from themeum/feat/new-secret-key-policy
shewa12 Oct 19, 2026
167943e
Refactor variable naming
shewa12 Oct 19, 2026
9685319
Fix: Called updated_option instead of get_option
shewa12 Oct 19, 2026
3041619
Use quiz answer stats for earned marks
sazedul-haque Oct 19, 2026
50a1584
Handle floats without decimal parts
sazedul-haque Oct 19, 2026
9ca3130
Skip required validation for revealed answers
sazedul-haque Oct 19, 2026
600e7d7
Handle missing content bank question flags
sazedul-haque Oct 19, 2026
a0313f9
Is reservation required check added
shewa12 Oct 19, 2026
5556047
Merge branch 'dev' into 4.2.0
sazedul-haque Oct 19, 2026
afca6c5
Merge pull request #3107 from themeum/fix-quiz-empty-submission
shewa12 Oct 19, 2026
8bdd809
get_json() issue in `HttpHelper`
sanjana4khan Oct 22, 2026
bd51745
refactor the comment
sanjana4khan Oct 22, 2026
2158940
Merge pull request #3109 from themeum/zoom-issue-free
harunollyo Oct 22, 2026
7c7281d
Enhance REST API access control for course ratings and content visibi…
saadman30 Oct 23, 2026
1c2d4ae
Update course rating API documentation for clarity and detail
saadman30 Oct 23, 2026
b8d2487
Refactor schema to avoid duplication
shewa12 Oct 23, 2026
bc5cd11
Enhance Q&A access control and update REST API permission handling
saadman30 Oct 23, 2026
1ba79fa
Enhance clipboard functionality with feedback notifications
saadman30 Oct 23, 2026
4429c19
Refactor Q&A access control logic in `has_qna_access` method
saadman30 Oct 23, 2026
2ca1add
Revert Q_And_A has_qna_access to leave post-status gating in REST only.
saadman30 Oct 23, 2026
b95cea5
Resume quizzes at the first unanswered question
sazedul-haque Oct 23, 2026
6b87d70
Merge pull request #3119 from themeum/fix-quiz-reload-issue
shewa12 Oct 23, 2026
dce2f3c
Refactor coupon usage count query
shewa12 Oct 23, 2026
468db9d
User id check added on the has_user_usage_limit
shewa12 Oct 23, 2026
ce9b5f8
Clear coupon reservation on Order trash or cancelled
shewa12 Oct 26, 2026
a017029
Removed the expire date update
shewa12 Oct 26, 2026
45e73e3
Code refactor
shewa12 Oct 26, 2026
ab21e5f
Refactor coupon usage & user usage check methods
shewa12 Oct 26, 2026
88f524c
Refactor coupon_usages table migration
shewa12 Oct 26, 2026
c4dc4be
Merge pull request #3106 from themeum/coupon-usage-refactor
shewa12 Oct 26, 2026
afe2af3
Merge pull request #3111 from themeum/fix/rest-api-status-filter
shewa12 Oct 26, 2026
5099205
Release date updated
shewa12 Oct 26, 2026
ceccb15
Fix: Coupon usage limit is not working
shewa12 Oct 26, 2026
f845e9d
Unittest added for the CouponModel
shewa12 Oct 27, 2026
60768cf
Merge pull request #3126 from themeum/4.2.0
shewa12 Oct 27, 2026
b656dc3
REST API content updated
shewa12 Oct 27, 2026
ffc51e6
Merge pull request #3129 from themeum/content-update
harunollyo Oct 27, 2026
503d1bd
Changelog updated
shewa12 Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
**ClickUp ticket link:**

## Description

<!-- Describe what changed and why. -->

## Testing

* [ ] Tested
* [ ] Not tested

## Use of AI Tool

**Used:** Yes / No

**Used for:**

<!-- Describe how AI tools were used, or write "N/A". -->
79 changes: 79 additions & 0 deletions .github/workflows/pr-description.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Validate PR Description

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
validate:
runs-on: ubuntu-latest
steps:
- name: Validate PR description
env:
PR_BODY: ${{ github.event.pull_request.body }}
shell: bash
run: |
set -euo pipefail

```
errors=0

# Check required sections and fields.
required_patterns=(
'^\*\*ClickUp ticket link:\*\*'
'^## Description'
'^## Testing'
'^- \[ \] Tested$'
'^- \[ \] Not tested$'
'^## Use of AI Tool'
'^\*\*Used:\*\* (Yes|No)$'
'^\*\*Used for:\*\*'
)

for pattern in "${required_patterns[@]}"; do
if ! grep -Eq "$pattern" <<< "$PR_BODY"; then
echo "::error::Missing or invalid PR description field: $pattern"
errors=1
fi
done

# Ensure exactly one testing option is selected.
tested_count=$(grep -Ec '^- \[[xX]\] Tested$' <<< "$PR_BODY" || true)
not_tested_count=$(grep -Ec '^- \[[xX]\] Not tested$' <<< "$PR_BODY" || true)

if (( tested_count + not_tested_count != 1 )); then
echo "::error::Select exactly one testing option: Tested or Not tested."
errors=1
fi

# Validate the ClickUp ticket link.
ticket_link=$(sed -n 's/^\*\*ClickUp ticket link:\*\*[[:space:]]*//p' <<< "$PR_BODY" | head -n 1)

if [[ ! "$ticket_link" =~ ^https://app\.clickup\.com/ ]]; then
echo "::error::Provide a valid ClickUp ticket URL starting with https://app.clickup.com/."
errors=1
fi

# Validate AI usage value.
if ! grep -Eq '^\*\*Used:\*\* (Yes|No)$' <<< "$PR_BODY"; then
echo "::error::Use of AI Tool must be Yes or No."
errors=1
fi

# Ensure the AI usage details are provided.
used_for=$(sed -n 's/^\*\*Used for:\*\*[[:space:]]*//p' <<< "$PR_BODY" | head -n 1)

if [[ -z "${used_for//[[:space:]]/}" ]]; then
echo "::error::Describe how AI was used, or provide N/A."
errors=1
fi

if (( errors != 0 )); then
exit 1
fi

echo "PR description validation passed."
```
4 changes: 2 additions & 2 deletions GDPR/Controllers/LegalConsent.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

use Tutor\GDPR\Models\{LegalConsents, LegalConsentLogs};
use Tutor\Helpers\ValidationHelper;
use TUTOR\Input;
use Tutor\Core\Input;
use Tutor\Traits\JsonResponse;
use WP_Error;

Expand Down Expand Up @@ -439,7 +439,7 @@ private function update_legal_consent( int $id, array $request ) {

$data = $this->prepare_legal_consent_data( $request, false );
if ( is_wp_error( $data ) ) {
$this->response_bad_request( __( 'Validation error', 'tutor' ), $data->errors, 400 );
$this->json_response( __( 'Validation error', 'tutor' ), $data->errors, 400 );
}

if ( isset( $data['consent_map'] ) && ! tutor_is_json( $data['consent_map'] ) ) {
Expand Down
84 changes: 37 additions & 47 deletions GDPR/Controllers/UserConsent.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

use Exception;
use Tutor\GDPR\Models\UserConsents;
use Tutor\Helpers\ValidationHelper;
use TUTOR\Input;
use Tutor\Traits\JsonResponse;
use WP_User;
Expand All @@ -33,7 +32,7 @@ class UserConsent extends BaseController {
*
* @since 4.0.0
*
* @var UserContents
* @var UserConsents
*/
private $model;

Expand Down Expand Up @@ -67,6 +66,7 @@ private function register_hooks() {
add_action( 'wp_ajax_tutor_user_consents', array( $this, 'handle_ajax_request' ) );
add_action( 'tutor_render_consent_logs_button', array( $this, 'render_consent_logs_button' ) );
add_action( 'tutor_render_consent_logs_modal', array( $this, 'render_consent_logs_modal' ) );
add_action( 'admin_footer-users.php', array( $this, 'render_consent_logs_modal' ) );
add_filter( 'manage_users_columns', array( $this, 'add_consent_logs_column' ) );
add_filter( 'manage_users_custom_column', array( $this, 'render_consent_logs_column' ), 10, 3 );
}
Expand All @@ -83,10 +83,6 @@ private function register_hooks() {
public function add_consent_logs_column( $columns ) {
$columns['consent_logs'] = __( 'Consent Logs', 'tutor' );

ob_start();
$this->render_consent_logs_modal();
$columns['consent_logs_modal'] = ob_get_clean();

return $columns;
}

Expand All @@ -110,7 +106,9 @@ public function render_consent_logs_column( $value, $column_name, $user_id ) {
return $value;
}

$value = '<button type="button" class="tutor-btn tutor-btn-outline-primary tutor-btn-sm" data-tutor-modal-target="tutor-consent-logs-modal" data-consent-logs-trigger data-user-id="' . esc_attr( $user_id ) . '" data-user-name="' . esc_attr( $user->display_name ) . '" data-user-joined="' . esc_attr( $user->user_registered ) . '" data-user-email="' . esc_attr( $user->user_email ) . '" data-user-login="' . esc_attr( $user->user_login ) . '" data-avatar-src="' . esc_url( tutor_utils()->get_user_avatar_url( $user_id ) ) . '"><i class="tutor-icon-eye-line tutor-mr-8" aria-hidden="true"></i>' . esc_html__( 'View Logs', 'tutor' ) . '</button>';
$value = '<button type="button" class="tutor-btn tutor-btn-outline-primary tutor-btn-sm" data-tutor-modal-target="tutor-consent-logs-modal" data-consent-logs-trigger data-user-id="'
. esc_attr( $user_id ) .
'"><i class="tutor-icon-eye-line tutor-mr-8" aria-hidden="true"></i>' . esc_html__( 'View Logs', 'tutor' ) . '</button>';

return $value;
}
Expand Down Expand Up @@ -186,21 +184,29 @@ public function handle_ajax_request(): void {
switch ( $user_action ) {
case 'all_consents_given_by_user':
$user_id = Input::post( 'user_id', 0, Input::TYPE_INT );
$user = $user_id ? get_userdata( $user_id ) : false;

$validate_user = ValidationHelper::validate(
array( 'user_id' => 'required|is_exists' ),
array( 'user_id' => $user_id )
);

if ( ! $validate_user->success ) {
if ( ! $user ) {
$this->response_bad_request( __( 'Invalid user ID', 'tutor' ) );
}

$consents = $this->get_all_consents_given_by_user( $user_id );
$html = $this->get_consent_logs_html( $user, $consents );

$this->json_response(
__( 'Consent fetched successfully', 'tutor' ),
$consents
array(
'html' => $html,
'has_logs' => ! empty( $consents ),
'logs' => $consents,
'user' => array(
'id' => $user->ID,
'name' => $user->display_name,
'login' => $user->user_login,
'email' => $user->user_email,
'joined' => $user->user_registered,
),
)
);

break;
Expand Down Expand Up @@ -318,6 +324,7 @@ function ( $record ) {
*/
private function build_and_store( $consent, $user_data, $display_key ) {
$build_consent = LegalConsent::build_consent_snapshot( $consent );

if ( ! empty( $build_consent ) ) {
$build_consent['user_id'] = $user_data->ID;
$build_consent['user_email'] = $user_data->user_email;
Expand Down Expand Up @@ -351,28 +358,6 @@ private function create( array $data ) {
return $user_consent_id;
}

/**
* Check if a user already gave consent for a display key and version.
*
* @since 4.0.0
*
* @param string $display_key Consent display key.
* @param string $version Consent version.
* @param int $user_id User ID. Defaults to current user.
*
* @return bool
*/
private function is_consent_given_by_user( string $display_key, string $version, int $user_id ): bool {
$user_data = get_userdata( $user_id );
if ( ! $user_data ) {
return false;
}

$given_consent = $this->model->is_consent_given_by_user( $user_id, $display_key, $version );

return $given_consent;
}

/**
* Render consent logs button.
* Called via action hook.
Expand All @@ -386,12 +371,6 @@ public function render_consent_logs_button( $user_data ): void {
if ( ! $user_id ) {
return;
}

$user_name = $user_data->display_name ?? '';
$user_joined = $user_data->user_registered ?? '';
$user_email = $user_data->user_email ?? '';
$user_login = $user_data->user_login ?? '';
$avatar_src = get_avatar_url( $user_id, array( 'size' => 40 ) );
?>
<div class="tutor-dropdown-parent">
<button type="button" class="tutor-iconic-btn" action-tutor-dropdown="toggle">
Expand All @@ -404,11 +383,6 @@ class="tutor-dropdown-item"
data-tutor-modal-target="tutor-consent-logs-modal"
data-consent-logs-trigger
data-user-id="<?php echo esc_attr( $user_id ); ?>"
data-user-name="<?php echo esc_attr( $user_name ); ?>"
data-user-joined="<?php echo esc_attr( $user_joined ); ?>"
data-user-email="<?php echo esc_attr( $user_email ); ?>"
data-user-login="<?php echo esc_attr( $user_login ); ?>"
data-avatar-src="<?php echo esc_url( $avatar_src ); ?>"
>
<i class="tutor-icon-file-text tutor-mr-8" aria-hidden="true"></i>
<span><?php esc_html_e( 'Consent Logs', 'tutor' ); ?></span>
Expand All @@ -418,6 +392,22 @@ class="tutor-dropdown-item"
<?php
}

/**
* Render consent logs content (timeline + user card).
*
* @since 4.1.3
*
* @param \WP_User $user User object.
* @param array $consents User consent records.
*
* @return string Rendered HTML.
*/
public function get_consent_logs_html( \WP_User $user, array $consents ): string {
ob_start();
include tutor()->path . 'views/templates/consent-logs-content.php';
return ob_get_clean();
}

/**
* Render consent logs modal.
* Called via action hook.
Expand Down
2 changes: 1 addition & 1 deletion GDPR/GDPR.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use Tutor\GDPR\Controllers\LegalConsent;
use Tutor\GDPR\Controllers\UserConsent;
use Tutor\GDPR\DB\DB;
use TUTOR\Singleton;
use Tutor\Core\Singleton;

defined( 'ABSPATH' ) || exit;

Expand Down
2 changes: 1 addition & 1 deletion GDPR/Models/UserConsents.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace Tutor\GDPR\Models;

use Tutor\GDPR\DB\UserConsents as Table;
use TUTOR\Input;
use Tutor\Core\Input;
use Tutor\Models\BaseModel;

defined( 'ABSPATH' ) || exit;
Expand Down
37 changes: 37 additions & 0 deletions assets/core/scss/components/_feature-disabled.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Feature Disabled / Access Denied Component
//
// Ported from legacy assets/src/scss/front/_denied.scss into the v4 design system.
// Used by templates/feature_disabled.php to render access-denied and feature-disabled
// states (e.g. when user registration is disabled) with proper v4 tokens and typography.

@use '../tokens' as *;
@use '../mixins' as *;

.tutor-disabled-wrapper {
max-width: 444px;
padding: $tutor-spacing-12 $tutor-spacing-none $tutor-spacing-8;
margin: auto;
@include tutor-flex(column, center, center);
text-align: center;

.tutor-disabled-content-wrapper {
@include tutor-flex(column, center, center);
text-align: center;

img {
margin-bottom: $tutor-spacing-6;
}

h3 {
@include tutor-typography('h4', 'medium', 'primary');
margin-top: $tutor-spacing-none;
margin-bottom: $tutor-spacing-3;
}

p {
@include tutor-typography('p2', 'regular', 'subdued');
margin-top: $tutor-spacing-none;
margin-bottom: $tutor-spacing-8;
}
}
}
1 change: 1 addition & 0 deletions assets/core/scss/components/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@
@forward 'time-input';
@forward 'status-select';
@forward 'star-rating';
@forward 'feature-disabled';
2 changes: 2 additions & 0 deletions assets/core/ts/utils/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const endpoints = {
QUIZ_ABANDON: 'tutor_quiz_abandon',
QUIZ_TIMEOUT: 'tutor_quiz_timeout',
QUIZ_ATTEMPT_SUBMIT: 'tutor_answering_quiz_question',
QUIZ_CHECK_ANSWER: 'tutor_quiz_check_answer',
REVIEW_QUIZ_ANSWERS: 'tutor_review_quiz_answers',
INSTRUCTOR_FEEDBACK: 'tutor_instructor_feedback',
SAVE_QUESTION_FEEDBACK: 'tutor_save_question_feedback',
Expand Down Expand Up @@ -168,6 +169,7 @@ const endpoints = {
IMPORT_SETTINGS_FREE: 'tutor_import_settings',
GET_IMPORT_EXPORT_HISTORY: 'tutor_pro_export_import_history',
DELETE_IMPORT_EXPORT_HISTORY: 'tutor_pro_delete_export_import_history',
EXPORT_ANALYTICS: 'export_analytics',

// CONTENT BANK
GET_CONTENT_BANK_COLLECTIONS: 'tutor_content_bank_collections',
Expand Down
32 changes: 32 additions & 0 deletions assets/core/ts/utils/security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,35 @@ export function escapeAttr(str: string): string {
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;');
}

/**
* Formula injection prevention prefixes (OWASP / CWE-1236)
*/
const FORMULA_INJECTION_PREFIXES = ['=', '+', '-', '@', '\t', '\r'] as const;

/**
* Sanitizes a single cell value for CSV export to prevent formula injection (OWASP / CWE-1236)
* and properly escape commas, double quotes, and newlines.
*
* @param rawVal - The raw value to sanitize
* @returns The sanitized cell string
*
* @since 4.2.0
*/
export function sanitizeCsvCell(rawVal: unknown): string {
if (rawVal === null || rawVal === undefined) {
return '';
}

let str = String(rawVal);

if (FORMULA_INJECTION_PREFIXES.some((prefix) => str.startsWith(prefix))) {
str = `'${str}`;
}

if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) {
return `"${str.replace(/"/g, '""')}"`;
}

return str;
}
Loading
Loading