Skip to content

Add page numbers to PDF footer#1236

Draft
pekiZG wants to merge 69 commits intoprep/v172from
feature/1234-pagination-in-mpdf-php-template
Draft

Add page numbers to PDF footer#1236
pekiZG wants to merge 69 commits intoprep/v172from
feature/1234-pagination-in-mpdf-php-template

Conversation

@pekiZG
Copy link
Copy Markdown

@pekiZG pekiZG commented Feb 22, 2025

Description

Added page numbering to the bottom of the PDF.

Related Issue

#1234

Motivation and Context

Page numbers were requested by @onny in #1234

Screenshots (if appropriate):

image
image

Pull Request Checklist

  • My code follows the code formatting guidelines.
  • I have an issue ID for this pull request.
  • I selected the corresponding branch.
  • I have rebased my changes on top of the corresponding branch.

Issue Type (Please check one or more)

  • Bugfix
  • Improvement of an existing Feature
  • New Feature

Summary by CodeRabbit

  • New Features

    • Added a toggle to include page numbers in generated PDFs.
  • Chores

    • Added/updated many CI workflows for testing, linting, dependency updates, releases, and translation sync; added workflow documentation.
    • Updated .gitignore.
  • Refactor

    • Widespread code formatting and style cleanups.
  • Improvements

    • Strengthened filename, referer/same-origin handling and related safety checks; enhanced bot-detection heuristics.

✏️ Tip: You can customize this high-level summary in your review settings.

@pekiZG pekiZG linked an issue Feb 22, 2025 that may be closed by this pull request
@pekiZG pekiZG changed the title [WIP] Add page numbers to PDF footer Add page numbers to PDF footer Feb 23, 2025
@pekiZG pekiZG requested a review from nielsdrost7 February 23, 2025 07:55
@pekiZG pekiZG self-assigned this Feb 23, 2025
@pekiZG pekiZG added the Feature Request Improvement or Feature Request label Feb 23, 2025
nielsdrost7 and others added 22 commits August 21, 2025 17:21
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
nielsdrost7 and others added 17 commits January 19, 2026 13:55
Removed specific branch and path triggers for pushes.
Removed specific push triggers for branches and tags.
Updated workflow triggers for Docker image build.
Updated workflow trigger to allow manual dispatch.
…in permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…in permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…in permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…in permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…in permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…n permissions (#1389)

* fixed error

Removed item discount display from invoice template.

* Potential fix for code scanning alert no. 5: Workflow does not contain permissions

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
)

* fixed error

Removed item discount display from invoice template.

* Potential fix for code scanning alert no. 8: Unsafe jQuery plugin

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Comment thread assets/core/js/jquery-ui.js Fixed
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/docker-phpmyadmin.yml (1)

19-22: The phpMyAdmin version test command will fail and should be replaced.

The phpmyadmin --version command does not exist in the official phpMyAdmin Docker image. The container has no CLI tool with that name, so this step will exit with "command not found" and break the workflow.

Replace with a test that verifies the web server is functional:

      - name: Build PHPMyAdmin Image
        run: |
          docker build -t phpmyadmin-test ./resources/docker/phpmyadmin
-         docker run --rm phpmyadmin-test phpmyadmin --version  # Test version
+         docker run --rm phpmyadmin-test apache2ctl configtest  # Verify Apache config is valid

Alternatively, verify PHP is functional with docker run --rm phpmyadmin-test php -v.

.github/workflows/test-frontend.yml (1)

14-15: Update Node.js test matrix to currently supported LTS versions.

Node.js 14 and 16 have reached end-of-life. Node.js 18 also reached end-of-life on April 30, 2025. Update the matrix to currently supported LTS versions (22 and 24).

         node: ['14', '16', '18']
+        node: ['22', '24']
🤖 Fix all issues with AI agents
In @.github/workflows/composer-update.yml:
- Around line 50-58: The current workflow step "Run Composer audit" uses a file
size check on audit-report.json which is a false positive because composer audit
emits {"advisories": {}} even when no issues exist; change the check to parse
the JSON and inspect the "advisories" object instead (for example, use jq or a
short python snippet to test if .advisories is empty) and set the GITHUB_OUTPUT
variable "vulnerabilities_found" to true only when .advisories contains entries;
update the step with the id "audit" to replace the -s audit-report.json
conditional with a JSON-aware check against .advisories.

In @.github/workflows/phpstan.yml:
- Around line 65-66: The "Comment on PR (if available)" step has an unreachable
condition (github.event_name == 'pull_request') because this workflow only
triggers on workflow_dispatch; update the workflow trigger or the step
condition: either add pull_request to the top-level on: triggers in this
phpstan.yml so the condition can be true, or change/remove the step's if: guard
and instead use a trigger/condition that matches how you intend to run it (e.g.,
use workflow_run or check github.event_name == 'workflow_run' / pass a PR
context from a caller) so the step can actually execute; locate the step by its
name "Comment on PR (if available)" to apply the change.

In @.github/workflows/phpunit.yml:
- Around line 37-49: The workflow contains Laravel-specific steps (php artisan
key:generate, php artisan test) and references .env.testing.example which are
not applicable to this CodeIgniter 3.3 project; remove or replace those lines
and instead run PHPUnit directly (e.g., vendor/bin/phpunit or phpunit) with the
repository's phpunit.xml configuration, ensure tests use the project's
configuration file (ipconfig.php) by copying or linking it into the test
environment before running tests, and delete any references to artisan commands
(php artisan key:generate, php artisan migrate, php artisan db:seed) so the CI
either runs CodeIgniter-compatible PHPUnit steps or the workflow is removed if
tests are not intended to run.

In @.github/workflows/pint.yml:
- Around line 93-101: The "Commit and Push Changes" workflow step currently
always runs git push and will fail for forked PRs because GITHUB_TOKEN is
read-only; update the step's if condition (the step named "Commit and Push
Changes" that already checks steps.verify_diff.outputs.has_changes) to also
require that the run is not a forked pull request by validating same-repo or
non-pull_request contexts (for example, require
github.event.pull_request.head.repo.full_name == github.repository or require
github.event_name != 'pull_request' / github.ref startsWith 'refs/heads/' for
manual/branch runs) so the push only runs when the action has push permission.

In @.github/workflows/quickstart.yml:
- Around line 64-65: The workflow currently runs the "Run database migrations"
step which invokes `php artisan migrate --force` unconditionally and will fail
without a DB; either remove or comment out that step (the step named "Run
database migrations" invoking `php artisan migrate --force`) if smoke tests
don't need DB, or alternatively ensure tests use an in-memory SQLite by updating
`.env.testing.example` to set DB_CONNECTION=sqlite and DB_DATABASE=:memory:, or
re-enable the MySQL service in the workflow so migrations can run; pick one
approach and apply it consistently so the `php artisan migrate --force` step no
longer fails.

In @.github/workflows/release.yml:
- Around line 425-439: The workflow step "Create GitHub Draft Pre-Release"
currently uses softprops/action-gh-release@v1 which is deprecated; update the
action reference to the v2 release (e.g., softprops/action-gh-release@v2 or
`@v2.5.0`) in the step that defines tag_name/name/body_path/files/draft/prerelease
so the newer action is used; after changing the version, verify input names
(tag_name, name, body_path, files, draft, prerelease) still match v2 docs and
adjust any differences in parameter names or behavior for the step if needed.

In @.github/workflows/test-php.yml:
- Around line 3-4: The workflow trigger was changed to only workflow_dispatch
which disables automatic PR testing; restore automatic runs by adding the
pull_request trigger back alongside workflow_dispatch (e.g., include a top-level
triggers block that contains both pull_request and workflow_dispatch) so tests
run on PR open/update while still allowing manual dispatch; update the
workflow's on: stanza to include both triggers.

In @.github/workflows/yarn-update.yml:
- Around line 107-131: The security-only upgrade step currently uses the command
referenced as yarn upgrade "$pkg" --latest which can bump across major versions;
change it to use yarn upgrade "$pkg" (remove --latest) so upgrades respect
existing semver ranges in package.json and avoid breaking major-version jumps
for the packages gathered in VULNERABLE_PACKAGES; ensure the loop that echoes
and calls yarn upgrade uses the updated command.

In `@application/helpers/file_security_helper.php`:
- Line 87: The code uses mb_rtrim (e.g., when building $realBaseWithSep in
file_security_helper.php) which exists only in PHP 8.4+, causing fatal errors on
the declared PHP 8.1+ target; replace mb_rtrim with the multibyte-safe rtrim (or
call rtrim with a UTF-8-aware normalization step) so the code runs on PHP
8.1–8.3, and apply the same replacement to other occurrences (index.php lines
around 209/248/275, phpmailer_helper.php usage, pdf_helper.php,
dropzone_helper.php) or alternatively update the project minimum PHP requirement
and Setup.php PHP version check to 8.4.0 to match mb_rtrim usage.

In `@application/helpers/mailer_helper.php`:
- Around line 248-260: Replace the brittle str_starts_with() origin check with a
same-origin comparison that parses and compares URL components: call parse_url()
on both base_url() and $_SERVER['HTTP_REFERER'], ensure scheme, host and port
(normalize default ports) match before assigning $redirect = $referer, and treat
root-relative referers (starting with "/") as safe; otherwise set $redirect =
base_url(); apply the same fix where str_starts_with is used (mailer_helper.php
logic around $_SERVER['HTTP_REFERER'] and the Sessions controller check) and
keep the final redirect($redirect) behavior unchanged.

In `@application/modules/clients/Enums/ClientTitleEnum.php`:
- Around line 20-31: The tryFrom method mis-handles array_search results and
returns the index instead of the matched string: change the truthy check to a
strict !== false (i.e. if ($searchResult !== false)) and set the returned
object's value to the actual string from getValues() (e.g. $returnObject->value
= $values[$searchResult] or simply $value) so lookups like 'mr' (index 0)
succeed and callers get the string value; keep the StdClass return pattern and
leave other behavior untouched.
🧹 Nitpick comments (5)
application/libraries/MY_Form_validation.php (1)

36-36: Consider using explicit if for conditional assignment.

Using short-circuit && for side effects (assignment) works correctly but is generally less readable than an explicit if statement. The intent is clearer with:

if (is_object($config)) {
    $this->CI = &$config;
}

This is a minor stylistic preference—the current code is functionally correct.

application/helpers/e-invoice_helper.php (1)

147-161: Consider explicit return null; for clarity.

In PHP, return; and return null; are functionally equivalent, so this isn't a behavioral change. However, given the function has multiple return paths (explicit null on validation failure, string on success, implicit null if file doesn't exist), an explicit return null; would improve readability and make the intent clearer.

🔧 Suggested improvement
     if ( ! is_valid_xml_config_id($xml_id)) {
         log_message('error', trans('log_invalid_xml_config_id_get_full_name') . ': ' . $xml_id);
 
-        return;
+        return null;
     }
.github/workflows/docker-publish.yml (1)

25-26: Conditionals checking for pull_request event are now redundant.

With the trigger changed to workflow_dispatch, the condition github.event_name != 'pull_request' will always be true. These checks are harmless but could be removed for clarity if desired.

Also applies to: 35-37, 56-56, 64-64

.github/workflows/test-frontend.yml (1)

19-22: Consider updating setup-node action to v4.

The actions/setup-node@v3 action could be updated to v4 for consistency with other checkout actions in the repository using @v4.

       - name: Setup node
-        uses: actions/setup-node@v3
+        uses: actions/setup-node@v4
         with:
           node-version: ${{ matrix.node }}
           cache: 'yarn'
.github/workflows/quickstart.yml (1)

59-62: Inconsistent .env setup compared to PHPUnit workflow.

This copies .env.testing.example to .env and runs key:generate without --env, while phpunit.yml copies to .env.testing and uses --env=testing. This inconsistency could cause confusion or unexpected behavior.

Consider aligning the approach:

♻️ Align with PHPUnit workflow pattern
       - name: Prepare Laravel environment
         run: |
-          cp .env.testing.example .env
-          php artisan key:generate
+          cp .env.testing.example .env.testing
+          php artisan key:generate --env=testing

Comment thread .github/workflows/composer-update.yml
Comment thread .github/workflows/phpstan.yml
Comment on lines +37 to +49
- name: Prepare Laravel environment
run: |
cp .env.testing.example .env.testing
php artisan key:generate --env=testing

#- name: Run Laravel migrations
# run: php artisan migrate --force --env=testing

#- name: Run Laravel seeds (optional)
# run: php artisan db:seed --force --env=testing

- name: Run PHPUnit
run: php artisan test --env=testing
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if .env.testing.example exists and verify its database configuration
if [ -f ".env.testing.example" ]; then
  echo "=== .env.testing.example exists ==="
  echo ""
  echo "=== Full contents of .env.testing.example ==="
  cat .env.testing.example
else
  echo "ERROR: .env.testing.example not found"
  exit 1
fi

Repository: InvoicePlane/InvoicePlane

Length of output: 106


🏁 Script executed:

#!/bin/bash
# Search for all .env files and .env.* files in the repository
echo "=== Searching for .env files ==="
find . -type f -name ".env*" | head -20

echo ""
echo "=== Checking if any example files exist ==="
find . -type f -name "*.example" | grep -E "(env|config)" | head -20

echo ""
echo "=== Checking root directory contents ==="
ls -la | grep -E "^-.*\.env" || echo "No .env files in root"

Repository: InvoicePlane/InvoicePlane

Length of output: 233


🏁 Script executed:

#!/bin/bash
# Check the project's actual configuration setup
echo "=== ipconfig.php.example contents (first 50 lines) ==="
head -50 ipconfig.php.example

echo ""
echo "=== Check if Laravel env() is used in codebase ==="
rg "\.env" --type php -l | head -10

echo ""
echo "=== Check Laravel bootstrap files ==="
find . -path "*/bootstrap/*.php" -type f | head -5

Repository: InvoicePlane/InvoicePlane

Length of output: 2426


🏁 Script executed:

#!/bin/bash
# Check composer.json to identify the actual framework
echo "=== composer.json dependencies (if exists) ==="
if [ -f "composer.json" ]; then
  cat composer.json | jq '.require // {}' 2>/dev/null | head -20
else
  echo "No composer.json found"
fi

echo ""
echo "=== Check for CodeIgniter or Laravel files ==="
echo "CodeIgniter index.php:"
ls -la index.php 2>/dev/null || echo "Not found"

echo ""
echo "Laravel artisan:"
ls -la artisan 2>/dev/null || echo "Not found"

echo ""
echo "=== Check project structure ==="
ls -la | grep -E "^d" | head -10

Repository: InvoicePlane/InvoicePlane

Length of output: 1197


This workflow is incompatible with the CodeIgniter framework used by this project.

The workflow uses Laravel-specific commands (php artisan key:generate, php artisan test) that do not exist in CodeIgniter 3.3. Additionally, it references a .env.testing.example file that neither exists nor is used by this project, which uses ipconfig.php for configuration instead. The entire workflow needs to be redesigned for CodeIgniter or removed if testing is not intended to be part of the build pipeline.

🤖 Prompt for AI Agents
In @.github/workflows/phpunit.yml around lines 37 - 49, The workflow contains
Laravel-specific steps (php artisan key:generate, php artisan test) and
references .env.testing.example which are not applicable to this CodeIgniter 3.3
project; remove or replace those lines and instead run PHPUnit directly (e.g.,
vendor/bin/phpunit or phpunit) with the repository's phpunit.xml configuration,
ensure tests use the project's configuration file (ipconfig.php) by copying or
linking it into the test environment before running tests, and delete any
references to artisan commands (php artisan key:generate, php artisan migrate,
php artisan db:seed) so the CI either runs CodeIgniter-compatible PHPUnit steps
or the workflow is removed if tests are not intended to run.

Comment thread .github/workflows/pint.yml
Comment thread .github/workflows/quickstart.yml Outdated
Comment thread .github/workflows/test-php.yml
Comment thread .github/workflows/yarn-update.yml
Comment thread application/helpers/file_security_helper.php Outdated
Comment thread application/helpers/mailer_helper.php
Comment thread application/modules/clients/Enums/ClientTitleEnum.php
Copy link
Copy Markdown
Contributor

@nielsdrost7 nielsdrost7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot
In @.github/workflows/composer-update.yml:

  • Around line 50-58: The current workflow step "Run Composer audit" uses a file
    size check on audit-report.json which is a false positive because composer audit
    emits {"advisories": {}} even when no issues exist; change the check to parse
    the JSON and inspect the "advisories" object instead (for example, use jq or a
    short python snippet to test if .advisories is empty) and set the GITHUB_OUTPUT
    variable "vulnerabilities_found" to true only when .advisories contains entries;
    update the step with the id "audit" to replace the -s audit-report.json
    conditional with a JSON-aware check against .advisories.

In @.github/workflows/phpstan.yml:

  • Around line 65-66: The "Comment on PR (if available)" step has an unreachable
    condition (github.event_name == 'pull_request') because this workflow only
    triggers on workflow_dispatch; update the workflow trigger or the step
    condition: either add pull_request to the top-level on: triggers in this
    phpstan.yml so the condition can be true, or change/remove the step's if: guard
    and instead use a trigger/condition that matches how you intend to run it (e.g.,
    use workflow_run or check github.event_name == 'workflow_run' / pass a PR
    context from a caller) so the step can actually execute; locate the step by its
    name "Comment on PR (if available)" to apply the change.

In @.github/workflows/phpunit.yml:

  • Around line 37-49: The workflow contains Laravel-specific steps (php artisan
    key:generate, php artisan test) and references .env.testing.example which are
    not applicable to this CodeIgniter 3.3 project; remove or replace those lines
    and instead run PHPUnit directly (e.g., vendor/bin/phpunit or phpunit) with the
    repository's phpunit.xml configuration, ensure tests use the project's
    configuration file (ipconfig.php) by copying or linking it into the test
    environment before running tests, and delete any references to artisan commands
    (php artisan key:generate, php artisan migrate, php artisan db:seed) so the CI
    either runs CodeIgniter-compatible PHPUnit steps or the workflow is removed if
    tests are not intended to run.

In @.github/workflows/pint.yml:

  • Around line 93-101: The "Commit and Push Changes" workflow step currently
    always runs git push and will fail for forked PRs because GITHUB_TOKEN is
    read-only; update the step's if condition (the step named "Commit and Push
    Changes" that already checks steps.verify_diff.outputs.has_changes) to also
    require that the run is not a forked pull request by validating same-repo or
    non-pull_request contexts (for example, require
    github.event.pull_request.head.repo.full_name == github.repository or require
    github.event_name != 'pull_request' / github.ref startsWith 'refs/heads/' for
    manual/branch runs) so the push only runs when the action has push permission.

In @.github/workflows/quickstart.yml:

  • Around line 64-65: The workflow currently runs the "Run database migrations"
    step which invokes php artisan migrate --force unconditionally and will fail
    without a DB; either remove or comment out that step (the step named "Run
    database migrations" invoking php artisan migrate --force) if smoke tests
    don't need DB, or alternatively ensure tests use an in-memory SQLite by updating
    .env.testing.example to set DB_CONNECTION=sqlite and DB_DATABASE=:memory:, or
    re-enable the MySQL service in the workflow so migrations can run; pick one
    approach and apply it consistently so the php artisan migrate --force step no
    longer fails.

In @.github/workflows/release.yml:

  • Around line 425-439: The workflow step "Create GitHub Draft Pre-Release"
    currently uses softprops/action-gh-release@v1 which is deprecated; update the
    action reference to the v2 release (e.g., softprops/action-gh-release@v2 or
    @v2.5.0) in the step that defines tag_name/name/body_path/files/draft/prerelease
    so the newer action is used; after changing the version, verify input names
    (tag_name, name, body_path, files, draft, prerelease) still match v2 docs and
    adjust any differences in parameter names or behavior for the step if needed.

In @.github/workflows/test-php.yml:

  • Around line 3-4: The workflow trigger was changed to only workflow_dispatch
    which disables automatic PR testing; restore automatic runs by adding the
    pull_request trigger back alongside workflow_dispatch (e.g., include a top-level
    triggers block that contains both pull_request and workflow_dispatch) so tests
    run on PR open/update while still allowing manual dispatch; update the
    workflow's on: stanza to include both triggers.

In @.github/workflows/yarn-update.yml:

  • Around line 107-131: The security-only upgrade step currently uses the command
    referenced as yarn upgrade "$pkg" --latest which can bump across major versions;
    change it to use yarn upgrade "$pkg" (remove --latest) so upgrades respect
    existing semver ranges in package.json and avoid breaking major-version jumps
    for the packages gathered in VULNERABLE_PACKAGES; ensure the loop that echoes
    and calls yarn upgrade uses the updated command.

In @application/helpers/file_security_helper.php:

  • Line 87: The code uses mb_rtrim (e.g., when building $realBaseWithSep in
    file_security_helper.php) which exists only in PHP 8.4+, causing fatal errors on
    the declared PHP 8.1+ target; replace mb_rtrim with the multibyte-safe rtrim (or
    call rtrim with a UTF-8-aware normalization step) so the code runs on PHP
    8.1–8.3, and apply the same replacement to other occurrences (index.php lines
    around 209/248/275, phpmailer_helper.php usage, pdf_helper.php,
    dropzone_helper.php) or alternatively update the project minimum PHP requirement
    and Setup.php PHP version check to 8.4.0 to match mb_rtrim usage.

In @application/helpers/mailer_helper.php:

  • Around line 248-260: Replace the brittle str_starts_with() origin check with a
    same-origin comparison that parses and compares URL components: call parse_url()
    on both base_url() and $_SERVER['HTTP_REFERER'], ensure scheme, host and port
    (normalize default ports) match before assigning $redirect = $referer, and treat
    root-relative referers (starting with "/") as safe; otherwise set $redirect =
    base_url(); apply the same fix where str_starts_with is used (mailer_helper.php
    logic around $_SERVER['HTTP_REFERER'] and the Sessions controller check) and
    keep the final redirect($redirect) behavior unchanged.

In @application/modules/clients/Enums/ClientTitleEnum.php:

  • Around line 20-31: The tryFrom method mis-handles array_search results and
    returns the index instead of the matched string: change the truthy check to a
    strict !== false (i.e. if ($searchResult !== false)) and set the returned
    object's value to the actual string from getValues() (e.g. $returnObject->value
    = $values[$searchResult] or simply $value) so lookups like 'mr' (index 0)
    succeed and callers get the string value; keep the StdClass return pattern and
    leave other behavior untouched.

Copy link
Copy Markdown
Contributor

Copilot AI commented Jan 19, 2026

@nielsdrost7 I've opened a new pull request, #1392, to work on those changes. Once the pull request is ready, I'll request review from you.

…1392)

* Initial plan

* Fix PHP 8.1+ compatibility: Replace mb_rtrim with rtrim and improve origin validation

Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>

* Fix workflow issues: JSON parsing, triggers, Laravel removal, fork checks

Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>

* Address code review feedback: Fix ClientTitleEnum value assignment and improve JSON validation

Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>
nielsdrost7 added a commit that referenced this pull request Jan 19, 2026
…1392)

* Initial plan

* Fix PHP 8.1+ compatibility: Replace mb_rtrim with rtrim and improve origin validation

Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>

* Fix workflow issues: JSON parsing, triggers, Laravel removal, fork checks

Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>

* Address code review feedback: Fix ClientTitleEnum value assignment and improve JSON validation

Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: nielsdrost7 <47660417+nielsdrost7@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
application/modules/mailer/helpers/phpmailer_helper.php (1)

140-146: Fix rtrim() character-mask behavior on line 144.

rtrim($attachment_path, '.pdf') removes characters from the right based on a character mask (., p, d, f), not a suffix. Filenames ending with p, d, or f lose extra characters: setup.pdfsetu.xml, staff.pdfsta.xml. This breaks XML file detection, attachment, and deletion. Use suffix-safe removal:

-        $xml_file = rtrim($attachment_path, '.pdf') . '.xml';
+        $xml_file = preg_replace('/\.pdf$/i', '', $attachment_path) . '.xml';
🤖 Fix all issues with AI agents
In @.github/workflows/composer-update.yml:
- Around line 93-99: The "Run smoke tests" workflow step currently invokes
Laravel-specific commands ("php artisan key:generate" and "php artisan test")
which are not applicable to CodeIgniter 3; update that step (named "Run smoke
tests") to remove those artisan commands and replace them with CI3-appropriate
checks such as running PHPUnit directly (e.g., "php vendor/bin/phpunit
--configuration phpunit.smoke.xml --env=testing") or a lightweight CI3 bootstrap
check (e.g., a script that loads index.php or runs a CI3 smoke test suite); also
remove or reconsider "continue-on-error: true" so the step reports meaningful
failures.

In @.github/workflows/phpstan.yml:
- Around line 33-45: The workflow step "Parse and format PHPStan results" (id:
parse-results) references a missing script
`.github/scripts/parse-phpstan-results.php`; fix by either adding that script to
the repository with the expected name and executable contents or update the step
to call the correct existing script path/filename that parses phpstan.json;
ensure the replacement path is committed and executable and that the step still
writes output to phpstan-report.md and returns appropriate exit codes on
failure.
- Around line 19-24: Replace the missing composite action reference by switching
the job step that currently uses "uses: ./.github/actions/setup-php-composer" to
a public action (e.g. "shivammathur/setup-php") and align the PHP version to
match the test matrix (change php-version from '8.4' to '8.2'); preserve the
php-extensions ('json') and composer-flags (--prefer-dist --no-interaction
--no-progress) in the step inputs for parity with the original intent, or
alternatively add the actual composite action file under
.github/actions/setup-php-composer if you prefer a local action.

In @.github/workflows/pint.yml:
- Around line 14-17: Update the pull_request branches list so the workflow
triggers on the repo's actual default branch: under the pull_request -> branches
array replace the non-existent "develop" entry with "development" (leaving
"master" as-is if needed) so the workflow's branch filter matches the real
branch names.

In @.github/workflows/release.yml:
- Around line 197-202: The workflow currently deletes all mPDF fonts except
those matching the pattern '! -name "DejaVu*"', which will break PDFs for
non-Latin scripts; replace that hardcoded filter with a configurable keep-list
by reading an environment variable (e.g., MPDF_KEEP_FONTS or MPDF_LOCALES) and
use it to build the find exclusion list (replace the '! -name "DejaVu*"' token
with generated '! -name "<font1>*" -o ! -name "<font2>*"' etc.), or
alternatively preserve a curated set of additional Unicode fonts (e.g.,
FreeSerif, Quivira) by changing the filter to '! -name "DejaVu*" -o ! -name
"FreeSerif*" -o ! -name "Quivira*"', and ensure the QR cleanup (rm -rf
vendor/mpdf/mpdf/src/QrCode/data/*) remains unchanged; also update workflow
docs/comments to explain the new MPDF_KEEP_FONTS variable and its impact on
non-Latin PDFs.
- Around line 76-79: Update the directory existence validation in the shell
if-block that currently checks for "resources/lang/en" (the Laravel path);
change it to verify the CodeIgniter directory "application/language/english"
instead so the if [ ! -d "resources/lang/en" ] check becomes an if [ ! -d
"application/language/english" ] check and the error message is updated to
reference application/language/english.

In @.github/workflows/yarn-update.yml:
- Around line 203-211: The workflow step with id "updated-packages" calls the
missing script ".github/scripts/generate-package-update-report.cjs" and will
fail when steps.check-changes.outputs.changes_detected == 'true'; fix by either
adding the script at .github/scripts/generate-package-update-report.cjs or make
the step robust—modify the step's run to first test for the script's existence
(or add a conditional using exists check) and skip generating the report if not
present, ensuring the step name "Get updated packages" and the condition
steps.check-changes.outputs.changes_detected remain correct.

In `@application/helpers/mailer_helper.php`:
- Around line 250-278: The root-relative referer check using
str_starts_with($referer, '/') is allowing protocol-relative URLs like
//evil.com; update the logic around $referer (the initial root-relative branch)
to only treat single-leading-slash paths as safe by rejecting or excluding
values that start with '//' (e.g., require str_starts_with($referer, '/') &&
!str_starts_with($referer, '//') or equivalent), leaving the existing
same-origin parse_url-based validation (the base_url/parse_url block that
compares $base_scheme/$base_host/$base_port with
$referer_scheme/$referer_host/$referer_port) unchanged and defaulting to
base_url() when validation fails.

In `@application/modules/sessions/controllers/Sessions.php`:
- Around line 528-573: The current _get_safe_referer method treats any string
starting with '/' as safe, which incorrectly allows protocol-relative referers
like '//evil.com'; update _get_safe_referer to explicitly reject referers that
start with '//' by checking for that prefix before the root-relative shortcut
(i.e., ensure str_starts_with($referer, '//') leads to using the safe default),
then keep the existing handling for single-leading-slash paths and full
same-origin URL checks so only true root-relative paths ('/path') and
same-origin absolute URLs are allowed.
🧹 Nitpick comments (4)
application/modules/clients/Enums/ClientTitleEnum.php (1)

20-32: Previous bugs fixed; consider adding strict comparison.

The !== false check and returning $value correctly address the issues from the previous review. For defensive coding, consider enabling strict type comparison in array_search to prevent unexpected matches from PHP's loose comparison:

♻️ Suggested improvement
-        $searchResult = array_search($value, $values);
+        $searchResult = array_search($value, $values, true);
application/modules/sessions/controllers/Sessions.php (1)

323-360: Persist cleaned attempts to avoid session growth.

_is_ip_rate_limited_password_reset() filters out old timestamps but doesn’t write them back, so the session array can grow unbounded over long-lived sessions.

♻️ Suggested fix
     $attempts    = array_filter($attempts, function ($timestamp) use ($cutoff_time) {
         return $timestamp > $cutoff_time;
     });
+
+    // Persist cleaned attempts to avoid unbounded session growth
+    $this->session->set_userdata($session_key, $attempts);
.github/workflows/yarn-update.yml (1)

90-105: Yarn audit parsing is fragile for edge cases.

The grep-based approach (grep -c "auditAdvisory") works for typical cases but may produce unexpected results if the JSON contains the string "auditAdvisory" in other contexts (e.g., within package names or descriptions). Consider using jq for more robust parsing, similar to the Composer workflow.

♻️ Optional: Use jq for more robust parsing
       - name: Run Yarn audit
         id: audit
         continue-on-error: true
         run: |
           yarn audit --json > audit-report.json || true
           if [ -s audit-report.json ]; then
-            VULNERABILITIES=$(grep -c "auditAdvisory" audit-report.json || echo "0")
+            # Count actual advisory entries more reliably
+            VULNERABILITIES=$(grep -c '"type":"auditAdvisory"' audit-report.json || echo "0")
             if [ "$VULNERABILITIES" -gt 0 ]; then
               echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT
               echo "vulnerability_count=$VULNERABILITIES" >> $GITHUB_OUTPUT
.github/workflows/pint.yml (1)

28-32: Checkout ref is empty on workflow_dispatch; use a conditional ref.

github.head_ref is only set for PRs. For manual runs, this can resolve to an empty ref and may ignore the selected branch. Consider selecting github.ref for non-PR runs.

✅ Suggested fix
       - name: Checkout repository
         uses: actions/checkout@v4
         with:
-          ref: ${{ github.head_ref }}
+          ref: ${{ github.event_name == 'pull_request' && github.head_ref || github.ref }}
           token: ${{ secrets.GITHUB_TOKEN }}

Comment on lines +93 to +99
- name: Run smoke tests
if: steps.check-changes.outputs.changes_detected == 'true'
run: |
cp .env.testing.example .env.testing
php artisan key:generate --env=testing
php artisan test --configuration=phpunit.smoke.xml --env=testing
continue-on-error: true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Smoke tests use Laravel artisan commands, but this is a CodeIgniter 3 project.

The quickstart workflow (line 64) explicitly states "CodeIgniter 3 project - Laravel artisan commands not applicable." However, this step runs php artisan key:generate and php artisan test, which will fail on a CodeIgniter 3 codebase.

While continue-on-error: true prevents workflow failure, the step provides no value and creates misleading output. Consider removing these Laravel-specific commands or replacing them with appropriate CodeIgniter 3 smoke tests.

🛠️ Suggested fix: Remove or replace with CI3-appropriate tests
       - name: Run smoke tests
         if: steps.check-changes.outputs.changes_detected == 'true'
         run: |
-          cp .env.testing.example .env.testing
-          php artisan key:generate --env=testing
-          php artisan test --configuration=phpunit.smoke.xml --env=testing
+          echo "Smoke tests not yet configured for CodeIgniter 3"
+          # TODO: Add appropriate smoke tests for CodeIgniter 3
         continue-on-error: true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Run smoke tests
if: steps.check-changes.outputs.changes_detected == 'true'
run: |
cp .env.testing.example .env.testing
php artisan key:generate --env=testing
php artisan test --configuration=phpunit.smoke.xml --env=testing
continue-on-error: true
- name: Run smoke tests
if: steps.check-changes.outputs.changes_detected == 'true'
run: |
echo "Smoke tests not yet configured for CodeIgniter 3"
# TODO: Add appropriate smoke tests for CodeIgniter 3
continue-on-error: true
🤖 Prompt for AI Agents
In @.github/workflows/composer-update.yml around lines 93 - 99, The "Run smoke
tests" workflow step currently invokes Laravel-specific commands ("php artisan
key:generate" and "php artisan test") which are not applicable to CodeIgniter 3;
update that step (named "Run smoke tests") to remove those artisan commands and
replace them with CI3-appropriate checks such as running PHPUnit directly (e.g.,
"php vendor/bin/phpunit --configuration phpunit.smoke.xml --env=testing") or a
lightweight CI3 bootstrap check (e.g., a script that loads index.php or runs a
CI3 smoke test suite); also remove or reconsider "continue-on-error: true" so
the step reports meaningful failures.

Comment on lines +19 to +24
- name: Setup PHP with Composer
uses: ./.github/actions/setup-php-composer
with:
php-version: '8.4'
php-extensions: 'json'
composer-flags: '--prefer-dist --no-interaction --no-progress'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

echo "=== Checking composer.json for PHP version ==="
if [ -f composer.json ]; then
  cat composer.json | jq '.require.php // .config.platform.php // "not found"' 2>/dev/null || cat composer.json | grep -i php
else
  echo "composer.json not found"
fi

echo -e "\n=== Checking other workflows for PHP version ==="
rg -n 'php-version' .github/workflows/ --type yaml 2>/dev/null || echo "No workflows found or rg not available"

echo -e "\n=== Checking if composite action exists ==="
if [ -d .github/actions/setup-php-composer ]; then
  echo "✓ Composite action exists"
  echo "Contents:"
  ls -la .github/actions/setup-php-composer/
else
  echo "✗ Composite action directory not found"
fi

echo -e "\n=== Listing all workflows ==="
ls -la .github/workflows/ 2>/dev/null || echo "No workflows directory"

Repository: InvoicePlane/InvoicePlane

Length of output: 2346


🏁 Script executed:

cat -n .github/workflows/phpstan.yml

Repository: InvoicePlane/InvoicePlane

Length of output: 3707


Composite action ./.github/actions/setup-php-composer does not exist—this workflow will fail.

The referenced action at line 20 cannot be found; the .github/actions/ directory does not exist in the repository. Additionally, PHP 8.4 used here differs from the test matrix in test-php.yml (which uses 8.2), creating inconsistency between static analysis and tests. Either create the missing composite action or use a standard action like shivammathur/setup-php.

🤖 Prompt for AI Agents
In @.github/workflows/phpstan.yml around lines 19 - 24, Replace the missing
composite action reference by switching the job step that currently uses "uses:
./.github/actions/setup-php-composer" to a public action (e.g.
"shivammathur/setup-php") and align the PHP version to match the test matrix
(change php-version from '8.4' to '8.2'); preserve the php-extensions ('json')
and composer-flags (--prefer-dist --no-interaction --no-progress) in the step
inputs for parity with the original intent, or alternatively add the actual
composite action file under .github/actions/setup-php-composer if you prefer a
local action.

Comment on lines +33 to +45
- name: Parse and format PHPStan results
id: parse-results
run: |
if [ ! -f phpstan.json ]; then
echo "PHPStan JSON output not found"
exit 1
fi
php .github/scripts/parse-phpstan-results.php phpstan.json > phpstan-report.md
exit_code=$?
if [ $exit_code -ne 0 ]; then
echo "Failed to parse PHPStan results"
exit $exit_code
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the parse script exists
echo "=== Checking for parse-phpstan-results.php ==="
fd -t f 'parse-phpstan-results.php' .github/

echo -e "\n=== Listing .github/scripts/ contents ==="
ls -la .github/scripts/ 2>/dev/null || echo "Directory not found"

Repository: InvoicePlane/InvoicePlane

Length of output: 178


The referenced PHP script .github/scripts/parse-phpstan-results.php does not exist.

The workflow will fail when attempting to execute this missing script. Either create the script at .github/scripts/parse-phpstan-results.php or update the workflow to reference the correct path if it exists elsewhere in the repository.

🤖 Prompt for AI Agents
In @.github/workflows/phpstan.yml around lines 33 - 45, The workflow step "Parse
and format PHPStan results" (id: parse-results) references a missing script
`.github/scripts/parse-phpstan-results.php`; fix by either adding that script to
the repository with the expected name and executable contents or update the step
to call the correct existing script path/filename that parses phpstan.json;
ensure the replacement path is committed and executable and that the step still
writes output to phpstan-report.md and returns appropriate exit codes on
failure.

Comment on lines +14 to +17
pull_request:
branches:
- master
- develop
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/pint.yml | head -50

Repository: InvoicePlane/InvoicePlane

Length of output: 1764


🏁 Script executed:

git branch -a | head -20

Repository: InvoicePlane/InvoicePlane

Length of output: 205


🏁 Script executed:

git branch -a | grep -i develop

Repository: InvoicePlane/InvoicePlane

Length of output: 155


Replace develop with development in pull_request branches.

The repository's default branch is development, but the workflow only triggers on master and develop. Since the develop branch does not exist in this repository, PRs targeting development will not trigger this workflow.

✅ Suggested fix
   pull_request:
     branches:
       - master
-      - develop
+      - development
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pull_request:
branches:
- master
- develop
pull_request:
branches:
- master
- development
🤖 Prompt for AI Agents
In @.github/workflows/pint.yml around lines 14 - 17, Update the pull_request
branches list so the workflow triggers on the repo's actual default branch:
under the pull_request -> branches array replace the non-existent "develop"
entry with "development" (leaving "master" as-is if needed) so the workflow's
branch filter matches the real branch names.

Comment on lines +76 to +79
if [ ! -d "resources/lang/en" ]; then
echo "ERROR: Missing required language directory: resources/lang/en" | tee -a "$LOG_FILE"
exit 1
fi
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Laravel-style translations
echo "Checking for Laravel-style translations:"
ls -la resources/lang/ 2>/dev/null || echo "resources/lang/ not found"

echo ""
echo "Checking for CodeIgniter-style translations:"
ls -la application/language/ 2>/dev/null || echo "application/language/ not found"

echo ""
echo "Listing root directory to understand project structure:"
ls -la | head -20

echo ""
echo "Checking for framework indicators (composer.json, framework files):"
if [ -f composer.json ]; then echo "composer.json found"; fi
if [ -f package.json ]; then echo "package.json found"; fi
if [ -d application ]; then echo "application/ directory found (CodeIgniter indicator)"; fi
if [ -d app ]; then echo "app/ directory found (Laravel indicator)"; fi

Repository: InvoicePlane/InvoicePlane

Length of output: 1819


🏁 Script executed:

#!/bin/bash
# Read the workflow file around the problematic lines
echo "=== Workflow file (lines 70-85) ==="
sed -n '70,85p' .github/workflows/release.yml

echo ""
echo "=== Contents of application/language/ ==="
ls -la application/language/

echo ""
echo "=== Contents of application/language/english (if exists) ==="
ls -la application/language/english/ 2>/dev/null | head -10

Repository: InvoicePlane/InvoicePlane

Length of output: 1445


Fix translation validation path to match CodeIgniter structure.

The validation at line 76 checks for resources/lang/en, which is a Laravel directory structure. This project is CodeIgniter 3 and uses application/language/english/. The current validation will always fail because this path does not exist. Update the validation to check application/language/english instead.

🤖 Prompt for AI Agents
In @.github/workflows/release.yml around lines 76 - 79, Update the directory
existence validation in the shell if-block that currently checks for
"resources/lang/en" (the Laravel path); change it to verify the CodeIgniter
directory "application/language/english" instead so the if [ ! -d
"resources/lang/en" ] check becomes an if [ ! -d "application/language/english"
] check and the error message is updated to reference
application/language/english.

Comment on lines +197 to +202
echo "Cleaning mPDF fonts (keeping only DejaVu)..." | tee -a "$LOG_FILE"
find vendor/mpdf/mpdf/ttfonts -type f ! -name "DejaVu*" \
-delete 2>/dev/null || true

echo "Cleaning mPDF QR code data..." | tee -a "$LOG_FILE"
rm -rf vendor/mpdf/mpdf/src/QrCode/data/* 2>/dev/null || true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Deleting non-DejaVu fonts may break PDF generation for non-Latin scripts.

The mPDF cleanup removes all fonts except DejaVu (! -name "DejaVu*"). This will break PDF generation for languages requiring other font families (Chinese, Japanese, Korean, Arabic, Thai, etc.).

If international language support is required, consider either:

  1. Keeping additional Unicode fonts (e.g., FreeSerif, Quivira)
  2. Making font retention configurable based on target locales
  3. Documenting this limitation for users who need non-Latin PDFs
🛠️ Suggested fix: Retain fonts for broader Unicode coverage
           echo "Cleaning mPDF fonts (keeping only DejaVu)..." | tee -a "$LOG_FILE"
-          find vendor/mpdf/mpdf/ttfonts -type f ! -name "DejaVu*" \
+          # Keep DejaVu for Latin scripts and common Unicode fonts for broader coverage
+          find vendor/mpdf/mpdf/ttfonts -type f \
+            ! -name "DejaVu*" \
+            ! -name "Free*" \
             -delete 2>/dev/null || true
🤖 Prompt for AI Agents
In @.github/workflows/release.yml around lines 197 - 202, The workflow currently
deletes all mPDF fonts except those matching the pattern '! -name "DejaVu*"',
which will break PDFs for non-Latin scripts; replace that hardcoded filter with
a configurable keep-list by reading an environment variable (e.g.,
MPDF_KEEP_FONTS or MPDF_LOCALES) and use it to build the find exclusion list
(replace the '! -name "DejaVu*"' token with generated '! -name "<font1>*" -o !
-name "<font2>*"' etc.), or alternatively preserve a curated set of additional
Unicode fonts (e.g., FreeSerif, Quivira) by changing the filter to '! -name
"DejaVu*" -o ! -name "FreeSerif*" -o ! -name "Quivira*"', and ensure the QR
cleanup (rm -rf vendor/mpdf/mpdf/src/QrCode/data/*) remains unchanged; also
update workflow docs/comments to explain the new MPDF_KEEP_FONTS variable and
its impact on non-Latin PDFs.

Comment on lines +203 to +211
- name: Get updated packages
if: steps.check-changes.outputs.changes_detected == 'true'
id: updated-packages
run: |
# Generate readable package update report
node .github/scripts/generate-package-update-report.cjs
echo "UPDATED_PACKAGES<<EOF" >> $GITHUB_OUTPUT
cat updated-packages.txt >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the external script exists
if [ -f ".github/scripts/generate-package-update-report.cjs" ]; then
  echo "✓ Script exists"
  echo "=== Script content (first 30 lines) ==="
  head -30 .github/scripts/generate-package-update-report.cjs
else
  echo "✗ Script NOT FOUND: .github/scripts/generate-package-update-report.cjs"
  echo "Available scripts in .github/scripts/:"
  ls -la .github/scripts/ 2>/dev/null || echo "Directory does not exist"
fi

Repository: InvoicePlane/InvoicePlane

Length of output: 203


🏁 Script executed:

#!/bin/bash
# Verify the workflow file exists and check for any similar scripts
echo "=== Checking if yarn-update.yml exists ==="
if [ -f ".github/workflows/yarn-update.yml" ]; then
  echo "✓ Workflow file exists"
  echo "=== Lines 203-211 from yarn-update.yml ==="
  sed -n '203,211p' .github/workflows/yarn-update.yml
else
  echo "✗ Workflow file not found"
fi

echo ""
echo "=== Searching for any package update related scripts ==="
find . -type f -name "*.cjs" -o -name "*.js" | grep -i "package\|update" | head -20

echo ""
echo "=== Checking if any .github subdirectories exist ==="
ls -la .github/ 2>/dev/null | head -20

Repository: InvoicePlane/InvoicePlane

Length of output: 1143


Missing required script will cause workflow failure.

The workflow step at lines 203-211 attempts to execute .github/scripts/generate-package-update-report.cjs, but this script and the .github/scripts/ directory do not exist in the repository. When steps.check-changes.outputs.changes_detected == 'true', this step will fail with "command not found". Either create the script or conditionally skip this step.

🤖 Prompt for AI Agents
In @.github/workflows/yarn-update.yml around lines 203 - 211, The workflow step
with id "updated-packages" calls the missing script
".github/scripts/generate-package-update-report.cjs" and will fail when
steps.check-changes.outputs.changes_detected == 'true'; fix by either adding the
script at .github/scripts/generate-package-update-report.cjs or make the step
robust—modify the step's run to first test for the script's existence (or add a
conditional using exists check) and skip generating the report if not present,
ensuring the step name "Get updated packages" and the condition
steps.check-changes.outputs.changes_detected remain correct.

Comment on lines +250 to 278
$referer = $_SERVER['HTTP_REFERER'] ?? '';

// Treat root-relative referers (starting with "/") as safe
if ( ! empty($referer) && str_starts_with($referer, '/')) {
$redirect = $referer;
} else {
$redirect = base_url(); // Safe default
// Parse and compare URL components for same-origin check
$base_url = base_url();
$base_parts = parse_url($base_url);
$referer_parts = parse_url($referer);

// Check if scheme, host, and port match (normalize default ports)
$base_scheme = $base_parts['scheme'] ?? 'http';
$base_host = $base_parts['host'] ?? '';
$base_port = $base_parts['port'] ?? ($base_scheme === 'https' ? 443 : 80);

$referer_scheme = $referer_parts['scheme'] ?? '';
$referer_host = $referer_parts['host'] ?? '';
$referer_port = $referer_parts['port'] ?? ($referer_scheme === 'https' ? 443 : 80);

if ( ! empty($referer) &&
$base_scheme === $referer_scheme &&
$base_host === $referer_host &&
$base_port === $referer_port) {
$redirect = $referer;
} else {
$redirect = base_url(); // Safe default
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Protocol-relative URLs bypass the root-relative check, creating an open redirect.

str_starts_with($referer, '/') on line 253 also matches //evil.com/path, which the browser interprets as a protocol-relative URL—redirecting users to an attacker-controlled domain.

Explicitly reject double-slash prefixes:

🔒 Proposed fix
             // Treat root-relative referers (starting with "/") as safe
-            if ( ! empty($referer) && str_starts_with($referer, '/')) {
+            if ( ! empty($referer) && str_starts_with($referer, '/') && ! str_starts_with($referer, '//')) {
                 $redirect = $referer;
             } else {

The same-origin validation logic for absolute URLs (lines 256-277) is well implemented with proper scheme/host/port comparison and default port normalization.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$referer = $_SERVER['HTTP_REFERER'] ?? '';
// Treat root-relative referers (starting with "/") as safe
if ( ! empty($referer) && str_starts_with($referer, '/')) {
$redirect = $referer;
} else {
$redirect = base_url(); // Safe default
// Parse and compare URL components for same-origin check
$base_url = base_url();
$base_parts = parse_url($base_url);
$referer_parts = parse_url($referer);
// Check if scheme, host, and port match (normalize default ports)
$base_scheme = $base_parts['scheme'] ?? 'http';
$base_host = $base_parts['host'] ?? '';
$base_port = $base_parts['port'] ?? ($base_scheme === 'https' ? 443 : 80);
$referer_scheme = $referer_parts['scheme'] ?? '';
$referer_host = $referer_parts['host'] ?? '';
$referer_port = $referer_parts['port'] ?? ($referer_scheme === 'https' ? 443 : 80);
if ( ! empty($referer) &&
$base_scheme === $referer_scheme &&
$base_host === $referer_host &&
$base_port === $referer_port) {
$redirect = $referer;
} else {
$redirect = base_url(); // Safe default
}
}
$referer = $_SERVER['HTTP_REFERER'] ?? '';
// Treat root-relative referers (starting with "/") as safe
if ( ! empty($referer) && str_starts_with($referer, '/') && ! str_starts_with($referer, '//')) {
$redirect = $referer;
} else {
// Parse and compare URL components for same-origin check
$base_url = base_url();
$base_parts = parse_url($base_url);
$referer_parts = parse_url($referer);
// Check if scheme, host, and port match (normalize default ports)
$base_scheme = $base_parts['scheme'] ?? 'http';
$base_host = $base_parts['host'] ?? '';
$base_port = $base_parts['port'] ?? ($base_scheme === 'https' ? 443 : 80);
$referer_scheme = $referer_parts['scheme'] ?? '';
$referer_host = $referer_parts['host'] ?? '';
$referer_port = $referer_parts['port'] ?? ($referer_scheme === 'https' ? 443 : 80);
if ( ! empty($referer) &&
$base_scheme === $referer_scheme &&
$base_host === $referer_host &&
$base_port === $referer_port) {
$redirect = $referer;
} else {
$redirect = base_url(); // Safe default
}
}
🤖 Prompt for AI Agents
In `@application/helpers/mailer_helper.php` around lines 250 - 278, The
root-relative referer check using str_starts_with($referer, '/') is allowing
protocol-relative URLs like //evil.com; update the logic around $referer (the
initial root-relative branch) to only treat single-leading-slash paths as safe
by rejecting or excluding values that start with '//' (e.g., require
str_starts_with($referer, '/') && !str_starts_with($referer, '//') or
equivalent), leaving the existing same-origin parse_url-based validation (the
base_url/parse_url block that compares $base_scheme/$base_host/$base_port with
$referer_scheme/$referer_host/$referer_port) unchanged and defaulting to
base_url() when validation fails.

Comment on lines 528 to 573
/**
* Validates that a referer URL is from the same domain
* to prevent open redirect vulnerabilities
* to prevent open redirect vulnerabilities.
*
* @param string $referer
*
* @return string Safe redirect URL
*/
private function _get_safe_referer($referer = '')
{
// Use provided referer or HTTP_REFERER
$referer = empty($referer) ? ($_SERVER['HTTP_REFERER'] ?? '') : $referer;

// If no referer, use default
if (empty($referer)) {
return 'sessions/passwordreset';
}

// Get base URL

// Treat root-relative referers (starting with "/") as safe
if (str_starts_with($referer, '/')) {
return $referer;
}

// Parse and compare URL components for same-origin check
$base_url = base_url();
$base_parts = parse_url($base_url);
$referer_parts = parse_url($referer);

// Check if scheme, host, and port match (normalize default ports)
$base_scheme = $base_parts['scheme'] ?? 'http';
$base_host = $base_parts['host'] ?? '';
$base_port = $base_parts['port'] ?? ($base_scheme === 'https' ? 443 : 80);

// Check if referer starts with base URL (same domain)
if (strpos($referer, $base_url) === 0) {
$referer_scheme = $referer_parts['scheme'] ?? '';
$referer_host = $referer_parts['host'] ?? '';
$referer_port = $referer_parts['port'] ?? ($referer_scheme === 'https' ? 443 : 80);

if ($base_scheme === $referer_scheme &&
$base_host === $referer_host &&
$base_port === $referer_port) {
return $referer;
}

// Referer is external or invalid, use safe default
return 'sessions/passwordreset';
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Block protocol‑relative referers to prevent open redirect.

//evil.com starts with / and will pass the root‑relative shortcut, enabling a protocol‑relative redirect. Treat // as unsafe.

🔒 Suggested fix
-        if (str_starts_with($referer, '/')) {
+        if (str_starts_with($referer, '/') && !str_starts_with($referer, '//')) {
             return $referer;
         }
🤖 Prompt for AI Agents
In `@application/modules/sessions/controllers/Sessions.php` around lines 528 -
573, The current _get_safe_referer method treats any string starting with '/' as
safe, which incorrectly allows protocol-relative referers like '//evil.com';
update _get_safe_referer to explicitly reject referers that start with '//' by
checking for that prefix before the root-relative shortcut (i.e., ensure
str_starts_with($referer, '//') leads to using the safe default), then keep the
existing handling for single-leading-slash paths and full same-origin URL checks
so only true root-relative paths ('/path') and same-origin absolute URLs are
allowed.

@nielsdrost7 nielsdrost7 marked this pull request as draft January 20, 2026 08:47
@nielsdrost7 nielsdrost7 changed the base branch from develop to prep/v172 February 16, 2026 15:12
@nielsdrost7 nielsdrost7 force-pushed the prep/v172 branch 2 times, most recently from d314f64 to 5b927da Compare April 16, 2026 15:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Feature Request Improvement or Feature Request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pagination in invoice php template

4 participants