%1$s installed, and %2$s activated', $plugins_count, $count_active ); ?>
- -| - | - | - | - | - |
|---|---|---|---|---|
| %s | %s | %s | %s | %s |
plugin.php.' ); ?>
- - - -Plugin list.' ); ?>
-diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 000000000..862d102d5
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,5 @@
+# End of line reformating
+3daf6593d89c608a6660a6c0b872eeb2607548ba
+# Convert all tabs to spaces
+a4cb0561f627c918cf304663fd32fd2b192f1565
+
diff --git a/.gitattributes b/.gitattributes
index 992f86d17..3e71d1166 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,8 +4,11 @@
# Generate pretty patches for PHP
*.php diff=php
+# Do not generate diff for vendored files
+includes/vendor/** -diff
+includes/vendor/** linguist-generated
+
# Exclude certain files or directories when generating an archive
-assets/less/ export-ignore
/.git* export-ignore
/*.md export-ignore
tests/ export-ignore
diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml
index 5dbbbc9e4..78388b571 100644
--- a/.github/workflows/auto-merge.yml
+++ b/.github/workflows/auto-merge.yml
@@ -11,24 +11,4 @@ permissions:
jobs:
dependabot:
name: Dependabot
- runs-on: ubuntu-latest
- if: ${{ github.actor == 'dependabot[bot]' }}
- steps:
- - name: Dependabot metadata
- id: dependabot-metadata
- uses: dependabot/fetch-metadata@v1.1.1
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Approve a PR
- if: steps.dependabot-metadata.outputs.update-type != 'version-update:semver-major'
- run: gh pr review --approve "$PR_URL"
- env:
- PR_URL: ${{ github.event.pull_request.html_url }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
- - name: Enable auto-merge for Dependabot PRs
- run: gh pr merge --auto --rebase "$PR_URL"
- env:
- PR_URL: ${{ github.event.pull_request.html_url }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ uses: YOURLS/.github/.github/workflows/auto-merge.yml@main
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 678d5bfea..272ef3c8a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,30 +2,30 @@ name: CI
on:
push:
- pull_request:
branches: [ master ]
+ pull_request:
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
jobs:
test:
- name: PHP
+ name: PHP ${{ matrix.php }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
- php: ['8.0']
- phpunit: ['latest']
+ php: ['8.1', '8.2', '8.3', '8.4', '8.5', '8.6']
include:
- - php: '7.2'
- phpunit: '8.5.13'
- - php: '7.3'
- phpunit: '8.5.13'
- - php: '7.4'
- phpunit: '8.5.13'
- coverage: xdebug
- flags: '--coverage-clover clover.xml'
-# - php: '8.1'
-# experimental: true
+ - php: '8.1'
+ coverage: xdebug
+ flags: '--coverage-clover clover.xml'
+
+ continue-on-error: ${{ matrix.php == '8.6' }}
services:
mysql:
@@ -36,17 +36,19 @@ jobs:
MYSQL_ALLOW_EMPTY_PASSWORD: false
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: yourls_tests
- options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
+ MARIADB_MYSQL_LOCALHOST_USER: 1
+ MARIADB_MYSQL_LOCALHOST_GRANTS: USAGE
+ options: --health-cmd="healthcheck.sh --su-mysql --connect --innodb_initialized" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- - uses: actions/checkout@v2.4.0
+ - uses: actions/checkout@v7
- name: Use PHP ${{ matrix.php }}
- uses: shivammathur/setup-php@2.16.0
+ uses: shivammathur/setup-php@2.37.2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, curl, zip, dom, simplexml, intl, pdo_mysql
- tools: phpunit:${{ matrix.phpunit }}
+ tools: phpunit
coverage: ${{ matrix.coverage }}
# - name: Validate composer.json and composer.lock
@@ -77,11 +79,6 @@ jobs:
cp tests/data/config/yourls-tests-config-ci.php user/config.php
- name: Test
- run: phpunit --configuration phpunit.xml.dist ${{ matrix.flags }}
+ run: phpunit --configuration phpunit.xml.dist --display-skipped --display-incomplete --display-deprecations --display-notices --display-warnings --display-errors ${{ matrix.flags }}
env:
DB_PORT: ${{ job.services.mysql.ports['3306'] }}
-
- - name: Upload Scrutinizer coverage
- uses: sudo-bot/action-scrutinizer@latest
- with:
- cli-args: "--format=php-clover clover.xml"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
deleted file mode 100644
index 0c50be464..000000000
--- a/.github/workflows/release.yml
+++ /dev/null
@@ -1,57 +0,0 @@
-name: Release
-
-on:
- push:
- tags:
- - '*'
-
-jobs:
- docker:
- name: Docker image
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2.4.0
- with:
- repository: YOURLS/docker
- ref: main
-
- - name: Get the version
- id: get_version
- run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
-
- - name: Set published version
- run: echo "${{ steps.get_version.outputs.VERSION }}" > yourls_version
-
- - name: Create Pull Request
- uses: peter-evans/create-pull-request@v3.12.1
- with:
- token: ${{ secrets.PAT }}
- commit-message: Bump YOURLS to ${{ steps.get_version.outputs.VERSION }}
- title: Bump YOURLS to ${{ steps.get_version.outputs.VERSION }}
- delete-branch: true
-
- charts:
- name: Helm charts
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2.4.0
- with:
- repository: YOURLS/charts
- ref: main
-
- - name: Get the version
- id: get_version
- run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\//}
-
- - name: Set published version
- run: |
- sed -i "s/appVersion:.*/appVersion: \"${{ steps.get_version.outputs.VERSION }}\"/" charts/yourls/Chart.yaml
-
- - name: Create Pull Request
- uses: peter-evans/create-pull-request@v3.12.1
- with:
- token: ${{ secrets.PAT }}
- commit-message: Bump YOURLS to ${{ steps.get_version.outputs.VERSION }}
- title: Bump YOURLS to ${{ steps.get_version.outputs.VERSION }}
- delete-branch: true
- draft: true
diff --git a/.github/workflows/update-certificates.yml b/.github/workflows/update-certificates.yml
new file mode 100644
index 000000000..ace0cb805
--- /dev/null
+++ b/.github/workflows/update-certificates.yml
@@ -0,0 +1,91 @@
+name: Update certificates
+
+on:
+ # Run every Monday at 13:37
+ schedule:
+ - cron: '37 13 * * 1'
+ # And manually
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ # The concurrency group contains the workflow name and the branch name.
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ certificate-check:
+ name: "Check for updated certificate bundle"
+ # Don't run the cron job on forks.
+ if: ${{ github.event_name != 'schedule' || github.repository == 'YOURLS/YOURLS' }}
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
+
+ - name: Generate token
+ uses: actions/create-github-app-token@v3
+ id: app-token
+ with:
+ app-id: ${{ vars.BOT_APP_ID }}
+ private-key: ${{ secrets.BOT_PRIVATE_KEY }}
+
+ - name: Get GitHub App User ID
+ id: get-user-id
+ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+
+ - name: Get current certificate bundle
+ working-directory: ./includes/vendor/rmccue/requests/certificates
+ run: |
+ curl --remote-name https://curl.se/ca/cacert.pem
+ curl --remote-name https://curl.se/ca/cacert.pem.sha256
+
+ - name: Verify the checksum of the downloaded bundle
+ working-directory: ./includes/vendor/rmccue/requests/certificates
+ run: sha256sum --check cacert.pem.sha256
+
+ - name: "Debug info: Show git status"
+ run: git status -vv --untracked=all
+
+ - name: "Get date"
+ id: get-date
+ run: echo "DATE=$(/bin/date -u "+%F")" >> $GITHUB_OUTPUT
+
+ - name: Create pull request
+ uses: peter-evans/create-pull-request@v8
+ id: pull-request
+ with:
+ token: ${{ steps.app-token.outputs.token }}
+ author: "${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
+ base: master
+ branch: auto-update-cacert
+ delete-branch: true
+ commit-message: "Update certificates"
+ title: "Update certificates"
+ body: |
+ Updated certificates, last verified on ${{ steps.get-date.outputs.DATE }}.
+
+ Source: https://curl.se/docs/caextract.html
+ labels: |
+ dependencies
+
+ - name: Approve a PR
+ if: ${{ steps.pull-request.outputs.pull-request-url && steps.pull-request.outputs.pull-request-operation != 'none' }}
+ run: gh pr review --approve "$PR_URL"
+ env:
+ PR_URL: ${{ steps.pull-request.outputs.pull-request-url }}
+ GITHUB_TOKEN: ${{ github.token }}
+
+ - name: Enable Pull Request Automerge
+ if: ${{ steps.pull-request.outputs.pull-request-url && steps.pull-request.outputs.pull-request-operation != 'none' }}
+ run: gh pr merge --auto --rebase "$PR_URL"
+ env:
+ PR_URL: ${{ steps.pull-request.outputs.pull-request-url }}
+ GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/update-geoip.yml b/.github/workflows/update-geoip.yml
new file mode 100644
index 000000000..69d45dbd2
--- /dev/null
+++ b/.github/workflows/update-geoip.yml
@@ -0,0 +1,92 @@
+name: Update GeoIP DB
+
+on:
+ # Run every Monday at 13:37
+ schedule:
+ - cron: '37 13 * * 1'
+ # Run manually
+ workflow_dispatch:
+
+# Cancels all previous workflow runs for the same branch that have not yet completed.
+concurrency:
+ # The concurrency group contains the workflow name and the branch name.
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: write
+ pull-requests: write
+
+jobs:
+ update-geoip:
+ name: "Check for updated GeoIP DB"
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
+
+ - name: Generate token
+ uses: actions/create-github-app-token@v3
+ id: app-token
+ with:
+ app-id: ${{ vars.BOT_APP_ID }}
+ private-key: ${{ secrets.BOT_PRIVATE_KEY }}
+
+ - name: Get GitHub App User ID
+ id: get-user-id
+ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+
+ - name: Check if newer GeoIP DB
+ env:
+ MAXMIND_API_KEY: ${{ secrets.MAXMIND_API_KEY }}
+ run: |
+ URL="https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${MAXMIND_API_KEY}&suffix=tar.gz"
+ REMOTE_MODIFIED=$(curl --silent --head "$URL" | grep "last-modified" | sed 's/last-modified: //')
+ REMOTE_CTIME=$(date -d "$REMOTE_MODIFIED" +%s)
+ LOCAL_MODIFIED=$(curl -fsSL https://api.github.com/repos/YOURLS/YOURLS/commits?path=includes/geo/GeoLite2-Country.mmdb | \
+ jq -r '.[0]["commit"]["author"]["date"]')
+ LOCAL_CTIME=$(date -d "$LOCAL_MODIFIED" +%s)
+ echo "Remote: $REMOTE_CTIME ($(date -d @$REMOTE_CTIME))"
+ echo "Local: $LOCAL_CTIME ($(date -d @$LOCAL_CTIME))"
+ if [ $LOCAL_CTIME -lt $REMOTE_CTIME ] ; then curl -fsSL "$URL" | tar -zvx -C includes/geo/ --strip-components 1 -- ; fi
+
+ - name: "Debug info: Show git status"
+ run: git status -vv --untracked=all
+
+ - name: "Get date"
+ id: get-date
+ run: echo "DATE=$(/bin/date -u "+%F")" >> $GITHUB_OUTPUT
+
+ - name: Create pull request
+ uses: peter-evans/create-pull-request@v8
+ id: pull-request
+ with:
+ token: ${{ steps.app-token.outputs.token }}
+ author: "${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
+ base: master
+ branch: auto-update-geoip
+ commit-message: "Update GeoIP DB"
+ title: "Update GeoIP DB"
+ body: |
+ Updated GeoIP database, last verified on ${{ steps.get-date.outputs.DATE }}.
+
+ Source: https://www.maxmind.com/en/account/login
+ labels: |
+ dependencies
+
+ - name: Approve a PR
+ if: ${{ steps.pull-request.outputs.pull-request-url && steps.pull-request.outputs.pull-request-operation != 'none' }}
+ run: gh pr review --approve "$PR_URL"
+ env:
+ PR_URL: ${{ steps.pull-request.outputs.pull-request-url }}
+ GITHUB_TOKEN: ${{ github.token }}
+
+ - name: Enable Pull Request Automerge
+ if: ${{ steps.pull-request.outputs.pull-request-url && steps.pull-request.outputs.pull-request-operation != 'none' }}
+ run: gh pr merge --auto --rebase "$PR_URL"
+ env:
+ PR_URL: ${{ steps.pull-request.outputs.pull-request-url }}
+ GITHUB_TOKEN: ${{ github.token }}
diff --git a/.github/workflows/update-translations.yml b/.github/workflows/update-translations.yml
new file mode 100644
index 000000000..15f7d164d
--- /dev/null
+++ b/.github/workflows/update-translations.yml
@@ -0,0 +1,115 @@
+name: Update translations
+
+on:
+ push:
+ tags:
+ - '*'
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+jobs:
+ translations-check:
+ name: Check for updated translations
+ if: ${{ github.event_name != 'schedule' || github.repository == 'YOURLS/YOURLS' }}
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
+
+ - name: Install GNU gettext
+ run: sudo apt-get install gettext
+
+ - name: Get version
+ id: get-version
+ run: echo "yourls-version=$(php -r 'require "includes/version.php"; echo YOURLS_VERSION;')" >> $GITHUB_OUTPUT
+
+ - name: Extract translations
+ env:
+ YOURLS_VERSION: ${{ steps.get-version.outputs.yourls-version }}
+ run: |
+ find . -name "*.php" ! -path "./user/*" ! -path "./tests/*" ! -path "./includes/vendor/*" \
+ | xargs xgettext \
+ --output=YOURLS.pot --package-name=YOURLS --package-version=$YOURLS_VERSION --foreign-user \
+ --add-location --language=PHP --from-code=UTF-8 --sort-by-file \
+ --keyword=yourls__ \
+ --keyword=yourls_e \
+ --keyword=yourls_s \
+ --keyword=yourls_se \
+ --keyword=yourls_esc_attr__ \
+ --keyword=yourls_esc_html__ \
+ --keyword=yourls_x \
+ --keyword=yourls_ex \
+ --keyword=yourls_esc_attr_x \
+ --keyword=yourls_esc_html_x \
+ --keyword=yourls_n:1,2 \
+ --keyword=yourls_nx:1,2 \
+ --keyword=yourls_n_noop:1,2 \
+ --keyword=yourls_nx_noop:1,2
+
+ - uses: actions/upload-artifact@v7
+ with:
+ name: YOURLS-pot
+ path: YOURLS.pot
+
+ translations-submit:
+ name: Submit updated translations
+ if: ${{ github.event_name == 'workflow_dispatch' && github.repository == 'YOURLS/YOURLS' }}
+ runs-on: ubuntu-latest
+ needs:
+ - translations-check
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v7
+ with:
+ repository: YOURLS/YOURLS.pot
+
+ - uses: actions/download-artifact@v8
+ with:
+ name: YOURLS-pot
+
+ - name: Generate token
+ uses: actions/create-github-app-token@v3
+ id: app-token
+ with:
+ app-id: ${{ vars.BOT_APP_ID }}
+ private-key: ${{ secrets.BOT_PRIVATE_KEY }}
+ owner: ${{ github.repository_owner }}
+
+ - name: Get GitHub App User ID
+ id: get-user-id
+ run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT"
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+
+ - name: Show git status
+ run: git status -vv --untracked=all
+
+ - name: Get date
+ id: get-date
+ run: echo "DATE=$(/bin/date -u "+%F")" >> $GITHUB_OUTPUT
+
+ - name: Create pull request
+ uses: peter-evans/create-pull-request@v8
+ id: pull-request
+ with:
+ token: ${{ steps.app-token.outputs.token }}
+ author: "${{ steps.app-token.outputs.app-slug }}[bot] <${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>"
+ base: main
+ branch: auto-update-translations
+ delete-branch: true
+ commit-message: "Update translations"
+ title: "Update translations"
+ body: |
+ Updated translations, last verified on ${{ steps.get-date.outputs.DATE }}.
+ labels: |
+ dependencies
+
+ - name: Enable Pull Request Automerge
+ if: ${{ steps.pull-request.outputs.pull-request-url && steps.pull-request.outputs.pull-request-operation != 'none' }}
+ run: gh pr merge --auto --rebase "$PR_URL"
+ env:
+ PR_URL: ${{ steps.pull-request.outputs.pull-request-url }}
+ GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
diff --git a/.gitignore b/.gitignore
index 4f03131e2..c05b5b456 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,8 @@ includes/**/tests/
build/
coverage/
phpunit.xml
+.phpunit.result.cache
+.phpunit.cache
tests/yourls-tests-config.php
tests/vendor/
tests/data/auth/config-test-auth-hashed.php
@@ -45,6 +47,8 @@ Thumbs.db
Desktop.ini
# Mac crap
.DS_Store
-# NetBeans files
+# IDE files
/nbproject/
.idea
+.vs
+.vscode
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9bcda616f..1c008d628 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,98 @@ YOURLS Changelog
_This file lists the main changes through all versions of YOURLS.
For a much more detailed list, simply refer to [commit messages](https://github.com/YOURLS/YOURLS/commits/master)._
+1.10.4
+---
+- fixed: Prevent [XSS](https://github.com/YOURLS/YOURLS/security/advisories/GHSA-5h77-88j3-r659) in stat pages through referrers (#4107)
+- added: Localization support for date and time display (#4054)
+- improved: Improve shunt filters (#4058)
+- fixed: Notice overlapping logo in admin panel (#4069)
+- fixed: Flag password file as user auth from environment variables (#4066)
+- added: Filter SQL queries (#4064)
+- improved: Improve debug functions and logic (#4089)
+- improved: Make tests debugging easier (#4104)
+
+1.10.3
+---
+- added: testing on PHP 8.5 & 8.6 (#4036)
+- added: function to get reserved URLs from global variable (#3999)
+- added: database index on long URLs, for faster searches (#4013)
+- added: `$context` parameter of `yourls_get_db()` (#4020)
+- changed: hide referrers on public statistics page by default (#4005)
+- changed: set the infos cache of the keyword after a url is added/updated/deleted (#4008)
+- changed: fix hover-actions in tables for screen reader users (#4025)
+- changed: confirm link deletion with a modal dialog (#3932)
+- fixed: upgrade spatie/array-to-xml to address deprecation warnings in newer PHP version (#4037)
+- fixed: filtering for links with more than 0 clicks (#3977)
+- fixed: case-sensitivity in admin search (#3997)
+- fixed: missing or invalid reserved URL configuration causing crash (#3999)
+- fixed: handling of invalid charsets on remote sites (#4007)
+- fixed: preserve backslashes in URLs (#4000)
+- fixed: validate JSONP callback names (#4030)
+- fixed: enhance configuration template formatting (#3994)
+- fixed: minor cleanup (#3979, #3984, #3988)
+
+1.10.2
+---
+- fixed: `admin/tools.php` now uses `yourls_get_nonce_life()` (#3906)
+- fixed: "Display 1 to 0 of 0 URLs" on admin list page (#3910)
+- fixed: replace deprecated `get_all_options` filter with an action (#3683)
+- fixed: defer loading text domain after plugins (#3679)
+- removed: Gandi references (#3929)
+
+1.10.1
+---
+- fixed: sandbox exceptions when disabling plugins (#3893)
+- fixed: stats date calculation are now correct (#3895)
+- fixed: unexpected warning raised on login page
+- removed: unsupported installation cases with Composer
+
+1.10.0
+---
+- added: Support PHP 8.3 & 8.4
+- removed: Support for PHP prior to 8.1 which is now minimal requirement
+- changed: Ensure all `statusCode`/`errorCode` API values are strings (#3756)
+- fixed: Results with 0 clicks on search (#3589)
+- fixed: Upgrade Aura.SQL to fix PHP 8.4 compatibility (#3852)
+- fixed: login page accessibility (#3660)
+- fixed: MySQL 8+ compatibility (#3828)
+- changed: Upgrade dependencies
+- changed: Update GeoIP DB
+- changed: Update certificates
+
+1.9.2
+---
+- added: Support PHP 8.2 (#3474)
+- improved: Googlebot indexing now filterable for plugins, for your SEO needs (#3517)
+- improved: Use safe sandbox for all included files (#3478)
+- fixed: bookmarklets with URL containing special chars (#3527)
+- fixed: unwanted cookies could interfere with YOURLS (#3516)
+- fixed: cosmetic bugs in the admin interface (#3485, #3431, #3518)
+- fixed: support usernames containing brackets (#3365)
+- updated: third party libs and binaries
+
+1.9.1
+---
+- fixed: error `Undefined constant "intval"` when upgrading (#3332)
+- fixed: warnings on PHP 8.1 (#3317)
+- fixed: incorrect HTTP status header with the API when shortening a duplicate (#3355)
+- fixed: no hyphen in random keywords (#3353)
+- added: required/suggested PHP extensions in composer.json (#3339)
+- updated: third party libs and binaries
+
+1.9
+---
+- removed : support for PHP prior to 7.4
+- improved: the API plugin with more plugin functions (#3281), a sandbox and a plugin uninstall procedure (#3282)
+- improved: inline documentation, [online documentation](https://docs.yourls.org/) and unit tests
+- improved: concurrency during mass shortening (#3233)
+- improved: minor security fixes - sanitize step name during upgrade (#3055),
+ nonce on the logout link (#3264), salt cookie with newer hash (#3278)
+- improved: Remove ozh/phpass library and use native PHP password_* functions (#3232)
+- added: more hooks in the admin view & search (#3265)
+- fixed: incorrect notice when "prefix and shorten" while not logged in (#3189)
+- fixed: UI sometimes not responsive after editing a URL (#3244)
+
1.8.2
---
- fixed: display SVG logo for IE 11 (#2864)
@@ -35,8 +127,8 @@ For a much more detailed list, simply refer to [commit messages](https://github.
1.7.9
---
- improved: compatibility of YOURLS with proxies and reversed proxies
-- improved: accept timestamped signature in API requests with [arbitrary hash](https://github.com/YOURLS/YOURLS/wiki/PasswordlessAPI#use-other-hash-algorithms-than-md5)
-- improved: YOURLS pages are now located in `user/` and [documented](https://github.com/YOURLS/YOURLS/wiki/Pages)
+- improved: accept timestamped signature in API requests with [arbitrary hash](https://docs.yourls.org/guide/advanced/passwordless-api.html#use-other-hash-algorithms-than-md5)
+- improved: YOURLS pages are now located in `user/` and [documented](https://docs.yourls.org/guide/extend/pages.html)
- improved: accessibility, with labels and aria tags in the main admin screen
- fixed: various little things here and also there
@@ -76,7 +168,7 @@ For a much more detailed list, simply refer to [commit messages](https://github.
1.7.1
---
- added: compatibility with PHP 7
-- added: allow hooks with closures (see [Advanced Hook Syntax](https://github.com/YOURLS/YOURLS/wiki/Advanced-Hook-Syntax))
+- added: allow hooks with closures (see [Advanced Hook Syntax](https://docs.yourls.org/development/hooks.html))
- improved: you can now search across all fields at once in the admin interface
- improved: bookmarklets are now human readable in the PHP source, and minified on the fly
- improved, still not perfect: support for URLs and page titles with encoded chars
diff --git a/README.md b/README.md
index f7546e5e6..7016843e8 100644
--- a/README.md
+++ b/README.md
@@ -6,20 +6,15 @@
> Your Own URL Shortener
- [](https://scrutinizer-ci.com/g/YOURLS/YOURLS/?branch=master)  [](https://packagist.org/packages/yourls/yourls) [](https://opencollective.com/yourls#contributors)
+[](https://github.com/YOURLS/YOURLS/actions/workflows/ci.yml) [](https://scrutinizer-ci.com/g/YOURLS/YOURLS/?branch=master)  [](https://packagist.org/packages/yourls/yourls) [](https://opencollective.com/yourls#contributors)
[](#sponsors)
**YOURLS** is a set of PHP scripts that will allow you to run Your Own URL Shortener, on **your** server. You'll have full control over your data, detailed stats, analytics, plugins, and more. It's free and open-source.
-## Quick Start
+## Getting Started
-Get YOURLS :
-* Download the latest [release](https://github.com/YOURLS/YOURLS/releases)
-* Using Composer? You can simply `composer create-project yourls/yourls .` in an empty directory.
-
-Install YOURLS:
-* Read [yourls.org](https://yourls.org) for starters
-* There are important additional information on the [Wiki documentation](https://github.com/YOURLS/YOURLS/wiki/).
+Check out the complete documentation on [docs.yourls.org](https://docs.yourls.org).
+It contains everything from beginners to experts.
## Community news, tips and tricks
@@ -34,7 +29,6 @@ Feature suggestion? Bug to report?
__Before opening any issue, please search for existing [issues](https://github.com/YOURLS/YOURLS/issues) (open and closed) and read the [Contributing Guidelines](https://github.com/YOURLS/.github/blob/master/CONTRIBUTING.md).__
-
## Backers
Do you use and enjoy YOURLS? [Become a backer](https://opencollective.com/yourls#backer) and show your support to our open source project.
@@ -95,7 +89,17 @@ Does your company use YOURLS? Ask your manager or marketing team if your company
[](https://opencollective.com/yourls/sponsor/17/website)
[](https://opencollective.com/yourls/sponsor/18/website)
[](https://opencollective.com/yourls/sponsor/19/website)
+[](https://opencollective.com/yourls/sponsor/20/website)
+[](https://opencollective.com/yourls/sponsor/21/website)
+[](https://opencollective.com/yourls/sponsor/22/website)
+
+
+
## License
diff --git a/admin/admin-ajax.php b/admin/admin-ajax.php
index 77f68ac2b..edfa16a49 100644
--- a/admin/admin-ajax.php
+++ b/admin/admin-ajax.php
@@ -10,43 +10,38 @@
yourls_no_frame_header();
if( !isset( $_REQUEST['action'] ) )
- die();
+ die();
// Pick action
$action = $_REQUEST['action'];
switch( $action ) {
- case 'add':
- yourls_verify_nonce( 'add_url', $_REQUEST['nonce'], false, 'omg error' );
- $return = yourls_add_new_link( $_REQUEST['url'], $_REQUEST['keyword'] );
- echo json_encode($return);
- break;
-
- case 'edit_display':
- yourls_verify_nonce( 'edit-link_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' );
- $row = yourls_table_edit_row ( $_REQUEST['keyword'] );
- echo json_encode( array('html' => $row) );
- break;
-
- case 'edit_save':
- yourls_verify_nonce( 'edit-save_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' );
- $return = yourls_edit_link( $_REQUEST['url'], $_REQUEST['keyword'], $_REQUEST['newkeyword'], $_REQUEST['title'] );
- echo json_encode($return);
- break;
-
- case 'delete':
- yourls_verify_nonce( 'delete-link_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' );
- $query = yourls_delete_link_by_keyword( $_REQUEST['keyword'] );
- echo json_encode(array('success'=>$query));
- break;
-
- case 'logout':
- // unused for the moment
- yourls_logout();
- break;
-
- default:
- yourls_do_action( 'yourls_ajax_'.$action );
+ case 'add':
+ yourls_verify_nonce( 'add_url', $_REQUEST['nonce'], false, 'omg error' );
+ $return = yourls_add_new_link( $_REQUEST['url'], $_REQUEST['keyword'], '', $_REQUEST['rowid'] );
+ echo json_encode($return);
+ break;
+
+ case 'edit_display':
+ yourls_verify_nonce( 'edit-link_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' );
+ $row = yourls_table_edit_row ( $_REQUEST['keyword'], $_REQUEST['id'] );
+ echo json_encode( array('html' => $row) );
+ break;
+
+ case 'edit_save':
+ yourls_verify_nonce( 'edit-save_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' );
+ $return = yourls_edit_link( $_REQUEST['url'], $_REQUEST['keyword'], $_REQUEST['newkeyword'], $_REQUEST['title'] );
+ echo json_encode($return);
+ break;
+
+ case 'delete':
+ yourls_verify_nonce( 'delete-link_'.$_REQUEST['id'], $_REQUEST['nonce'], false, 'omg error' );
+ $query = yourls_delete_link_by_keyword( $_REQUEST['keyword'] );
+ echo json_encode(array('success'=>$query));
+ break;
+
+ default:
+ yourls_do_action( 'yourls_ajax_'.$action );
}
diff --git a/admin/index.php b/admin/index.php
index 9a6d688d5..1e60a56c3 100644
--- a/admin/index.php
+++ b/admin/index.php
@@ -30,17 +30,20 @@
$search_in = $view_params->get_search_in();
$search_in_text = $view_params->get_param_long_name($search_in);
if( $search && $search_in && $search_in_text ) {
- $search_sentence = yourls_s( 'Searching for %1$s in %2$s.', yourls_esc_html( $search ), yourls_esc_html( $search_in_text ) );
- $search_text = $search;
- $search = str_replace( '*', '%', '*' . $search . '*' );
+ $search_sentence = yourls_s( 'Searching for %1$s in %2$s.', yourls_esc_html( $search ), yourls_esc_html( $search_in_text ) );
+ $search_text = $search;
+ $search = str_replace( '*', '%', '*' . $search . '*' );
if( $search_in == 'all' ) {
- $where['sql'] .= " AND CONCAT_WS('',`keyword`,`url`,`title`,`ip`) LIKE (:search)";
- // Search across all fields. The resulting SQL will be something like:
- // SELECT * FROM `yourls_url` WHERE CONCAT_WS('',`keyword`,`url`,`title`,`ip`) LIKE ("%ozh%")
- // CONCAT_WS because CONCAT('foo', 'bar', NULL) = NULL. NULL wins. Not sure if values can be NULL now or in the future, so better safe.
- // TODO: pay attention to this bit when the DB schema changes
+ $where['sql'] .= " AND `keyword` LIKE (:search)
+ OR `url` LIKE (:search)
+ OR `title` COLLATE utf8mb4_unicode_ci LIKE (:search) COLLATE utf8mb4_unicode_ci
+ OR `ip` LIKE (:search) ";
} else {
- $where['sql'] .= " AND `$search_in` LIKE (:search)";
+ $collate = '';
+ if( $search_in == 'title' ) {
+ $collate = ' COLLATE utf8mb4_unicode_ci';
+ }
+ $where['sql'] .= " AND `$search_in` $collate LIKE (:search) $collate";
}
$where['binds']['search'] = $search;
}
@@ -96,139 +99,139 @@
// Get URLs Count for current filter, total links in DB & total clicks
list( $total_urls, $total_clicks ) = array_values( yourls_get_db_stats() );
if ( !empty($where['sql']) ) {
- list( $total_items, $total_items_clicks ) = array_values( yourls_get_db_stats( $where ) );
+ list( $total_items, $total_items_clicks ) = array_values( yourls_get_db_stats( $where ) );
} else {
- $total_items = $total_urls;
- $total_items_clicks = false;
+ $total_items = $total_urls;
+ $total_items_clicks = false;
}
// This is a bookmarklet
if ( isset( $_GET['u'] ) or isset( $_GET['up'] ) ) {
- $is_bookmark = true;
- yourls_do_action( 'bookmarklet' );
-
- // No sanitization needed here: everything happens in yourls_add_new_link()
- if( isset( $_GET['u'] ) ) {
- // Old school bookmarklet: ?u= %1$s to %2$s of %3$s URLs' ), $display_on_page, $max_on_page, $total_items );
- if( $total_items_clicks !== false )
- echo ", " . sprintf( yourls_n( 'counting 1 click', 'counting %s clicks', $total_items_clicks ), yourls_number_format_i18n( $total_items_clicks ) );
- ?>. %1$s to %2$s of %3$s URLs' ), $display_on_page, $max_on_page, $total_items );
+ if( $total_items_clicks !== false )
+ echo ", " . sprintf( yourls_n( 'counting 1 click', 'counting %s clicks', $total_items_clicks ), yourls_number_format_i18n( $total_items_clicks ) );
+ }
+ ?>. %1$s links, %2$s clicks, and counting!' ), yourls_number_format_i18n( $total_urls ), yourls_number_format_i18n( $total_clicks ) ); ?> %1$s installed, and %2$s activated', $plugins_count, $count_active ); ?> plugin.php.' ); ?> Plugin list.' ); ?> %1$s installed, and %2$s activated', $plugins_count, $count_active ); ?> plugin.php.' ); ?> Plugin list.' ); ?> bookmarklets for easier link shortening and sharing.' ); ?> bookmarklets for easier link shortening and sharing.' ); ?> select text on the page you're viewing before clicking on your bookmarklet link" );
- ?> select text on the page you're viewing before clicking on your bookmarklet link" );
+ ?> Important Note: bookmarklets may fail on websites with https, especially the "Instant" bookrmarklets. There is nothing you can do about this.'); ?>
-
+
-
+
-
-
-
-
-
-
-
-
-
-
- $plugin ) {
-
- // default fields to read from the plugin header
- $fields = array(
- 'name' => 'Plugin Name',
- 'uri' => 'Plugin URI',
- 'desc' => 'Description',
- 'version' => 'Version',
- 'author' => 'Author',
- 'author_uri' => 'Author URI'
- );
-
- // Loop through all default fields, get value if any and reset it
- foreach( $fields as $field=>$value ) {
- if( isset( $plugin[ $value ] ) ) {
- $data[ $field ] = $plugin[ $value ];
- } else {
- $data[ $field ] = yourls__('(no info)');
- }
- unset( $plugin[$value] );
- }
-
- $plugindir = trim( dirname( $file ), '/' );
-
- if( yourls_is_active_plugin( $file ) ) {
- $class = 'active';
- $action_url = yourls_nonce_url( 'manage_plugins', yourls_add_query_arg( array('action' => 'deactivate', 'plugin' => $plugindir ), yourls_admin_url('plugins.php') ) );
- $action_anchor = yourls__( 'Deactivate' );
- } else {
- $class = 'inactive';
- $action_url = yourls_nonce_url( 'manage_plugins', yourls_add_query_arg( array('action' => 'activate', 'plugin' => $plugindir ), yourls_admin_url('plugins.php') ) );
- $action_anchor = yourls__( 'Activate' );
- }
-
- // Other "Fields: Value" in the header? Get them too
- if( $plugin ) {
- foreach( $plugin as $extra_field=>$extra_value ) {
- $data['desc'] .= "
-
-
-
-
-
\n$extra_field: $extra_value";
- unset( $plugin[$extra_value] );
- }
- }
-
- $data['desc'] .= '
' . yourls_s( 'plugin file location: %s', $file) . '';
-
- printf( " ",
- $class, $data['uri'], $data['name'], $data['version'], $data['desc'], $data['author_uri'], $data['author'], $action_url, $action_anchor
- );
-
- }
- ?>
-
- %s %s %s %s %s
+
+
+
+
+
+
+
+
+
+ $plugin ) {
+
+ // default fields to read from the plugin header
+ $fields = array(
+ 'name' => 'Plugin Name',
+ 'uri' => 'Plugin URI',
+ 'desc' => 'Description',
+ 'version' => 'Version',
+ 'author' => 'Author',
+ 'author_uri' => 'Author URI'
+ );
+
+ // Loop through all default fields, get value if any and reset it
+ foreach( $fields as $field=>$value ) {
+ if( isset( $plugin[ $value ] ) ) {
+ $data[ $field ] = $plugin[ $value ];
+ } else {
+ $data[ $field ] = yourls__('(no info)');
+ # If it's a URL, set to #
+ if( in_array( $field, array('uri', 'author_uri') ) ) {
+ $data[$field] = '#' . $data[$field];
+ }
+ }
+ unset( $plugin[$value] );
+ }
+
+ $plugindir = trim( dirname( $file ), '/' );
+
+ if( yourls_is_active_plugin( $file ) ) {
+ $class = 'active';
+ $action_url = yourls_nonce_url( 'manage_plugins', yourls_add_query_arg( array('action' => 'deactivate', 'plugin' => $plugindir ), yourls_admin_url('plugins.php') ) );
+ $action_anchor = yourls__( 'Deactivate' );
+ } else {
+ $class = 'inactive';
+ $action_url = yourls_nonce_url( 'manage_plugins', yourls_add_query_arg( array('action' => 'activate', 'plugin' => $plugindir ), yourls_admin_url('plugins.php') ) );
+ $action_anchor = yourls__( 'Activate' );
+ }
+
+ // Other "Fields: Value" in the header? Get them too
+ if( $plugin ) {
+ foreach( $plugin as $extra_field=>$extra_value ) {
+ $data['desc'] .= "
+
+
+
+
+
\n$extra_field: $extra_value";
+ unset( $plugin[$extra_value] );
+ }
+ }
+
+ $data['desc'] .= '
' . yourls_s( 'plugin file location: %s', $file) . '';
+
+ printf( " ",
+ $class, $data['uri'], $data['name'], $data['version'], $data['desc'], $data['author_uri'], $data['author'], $action_url, $action_anchor
+ );
+
+ }
+ ?>
+
+ %s %s %s %s %s
-
-
+
+
-
-
+
+
+
-
+
-
-
-
-
-
-
-
-
-
+
-
-
+
+
+
+
+
+
+
+
+
-
+
+
-
+
-
+
+
-
-
-
+
-
%s\" to the beginning of the current URL (right before its 'http://' part) and hit enter.", preg_replace('@https?://@', '', yourls_get_yourls_site()) . '/' ); ?>
+%s\" to the beginning of the current URL (right before its 'http://' part) and hit enter.", preg_replace('@https?://@', '', yourls_get_yourls_site()) . '/' ); ?>
-.
+.
- + - + -username and password parameters.' ); - echo "\n"; - yourls_e( "If you're worried about sending your credentials into the wild, you can also make API calls without using your login or your password, using a secret signature token." ); - ?>
+username and password parameters.' ); + echo "\n"; + yourls_e( "If you're worried about sending your credentials into the wild, you can also make API calls without using your login or your password, using a secret signature token." ); + ?>
-%s', yourls_auth_signature() ); ?>
+
%s', yourls_auth_signature() ); ?>
signature in your API requests. Example:' ); ?>
-/yourls-api.php?signature=&action=...
signature in your API requests. Example:' ); ?>
+/yourls-api.php?signature=&action=...
<?php
$timestamp = time();
// $time =
-$signature = md5( $timestamp . '' );
-// $signature = ""
+$signature = hash('sha256', $timestamp . '' );
+// $signature = ""
?>
- signature and timestamp in your API requests. Example:' ); ?>
-/yourls-api.php?timestamp=$timestamp&signature=$signature&action=...
- /yourls-api.php?timestamp=&signature=&action=...
signature and timestamp in your API requests. Example:' ); ?>
+/yourls-api.php?timestamp=$timestamp&signature=$signature&action=...
+ /yourls-api.php?timestamp=&signature=&action=...
Passwordless API page on the wiki.', 'https://yourls.org/passwordlessapi' ); ?> - API documentation for more', yourls_get_yourls_site() . '/readme.html#API' ); ?>
+Passwordless API page on the wiki.', 'https://yourls.org/passwordlessapi' ); ?> + API documentation for more', yourls_get_yourls_site() . '/readme.html#API' ); ?>
- + - + diff --git a/admin/upgrade.php b/admin/upgrade.php index 81333244b..957870cda 100644 --- a/admin/upgrade.php +++ b/admin/upgrade.php @@ -2,81 +2,79 @@ define( 'YOURLS_ADMIN', true ); define( 'YOURLS_UPGRADING', true ); require_once( dirname( __DIR__ ).'/includes/load-yourls.php' ); -require_once( YOURLS_INC.'/functions-upgrade.php' ); -require_once( YOURLS_INC.'/functions-install.php' ); yourls_maybe_require_auth(); yourls_html_head( 'upgrade', yourls__( 'Upgrade YOURLS' ) ); yourls_html_logo(); yourls_html_menu(); ?> - + ' . yourls_s( 'Upgrade not required. Go back to play!', yourls_admin_url('index.php') ) . ''; + echo '' . yourls_s( 'Upgrade not required. Go back to play!', yourls_admin_url('index.php') ) . '
'; } else { - /* - step 1: create new tables and populate them, update old tables structure, - step 2: convert each row of outdated tables if needed - step 3: - if applicable finish updating outdated tables (indexes etc) - - update version & db_version in options, this is all done! - */ + /* + step 1: create new tables and populate them, update old tables structure, + step 2: convert each row of outdated tables if needed + step 3: - if applicable finish updating outdated tables (indexes etc) + - update version & db_version in options, this is all done! + */ - // From what are we upgrading? - if ( isset( $_GET['oldver'] ) && isset( $_GET['oldsql'] ) ) { - $oldver = yourls_sanitize_version($_GET['oldver']); - $oldsql = (intval)($_GET['oldsql']); - } else { - list( $oldver, $oldsql ) = yourls_get_current_version_from_sql(); - } + // From what are we upgrading? + if ( isset( $_GET['oldver'] ) && isset( $_GET['oldsql'] ) ) { + $oldver = yourls_sanitize_version($_GET['oldver']); + $oldsql = intval($_GET['oldsql']); + } else { + list( $oldver, $oldsql ) = yourls_get_current_version_from_sql(); + } - // To what are we upgrading ? - $newver = YOURLS_VERSION; - $newsql = YOURLS_DB_VERSION; + // To what are we upgrading ? + $newver = YOURLS_VERSION; + $newsql = YOURLS_DB_VERSION; - // Verbose & ugly details - yourls_debug_mode(true); + // Verbose & ugly details + yourls_debug_mode(true); - // Let's go - $step = ( isset( $_GET['step'] ) ? intval( $_GET['step'] ) : 0 ); - switch( $step ) { + // Let's go + $step = ( isset( $_GET['step'] ) ? intval( $_GET['step'] ) : 0 ); + switch( $step ) { - default: - case 0: - ?> - -backup your database
(you should do this regularly anyway)' ); ?>
should happen, but this doesn't mean it won't happen, right? ;)" ); ?>
-something goes wrong, you'll see a message and hopefully a way to fix." ); ?>
-good for you, let it go :)' ); ?>
- - - - - - - - - "; + default: + case 0: + ?> + +backup your database
(you should do this regularly anyway)' ); ?>
should happen, but this doesn't mean it won't happen, right? ;)" ); ?>
+something goes wrong, you'll see a message and hopefully a way to fix." ); ?>
+good for you, let it go :)' ); ?>
+ + + + + + + + + "; - break; + break; - case 1: - case 2: - $upgrade = yourls_upgrade( $step, $oldver, $newver, $oldsql, $newsql ); - break; + case 1: + case 2: + $upgrade = yourls_upgrade( $step, $oldver, $newver, $oldsql, $newsql ); + break; - case 3: - $upgrade = yourls_upgrade( 3, $oldver, $newver, $oldsql, $newsql ); - echo '' . yourls__( 'Your installation is now up to date ! ' ) . '
'; - echo '' . yourls_s( 'Go back to the admin interface', yourls_admin_url('index.php') ) . '
'; - } + case 3: + $upgrade = yourls_upgrade( 3, $oldver, $newver, $oldsql, $newsql ); + echo '' . yourls__( 'Your installation is now up to date ! ' ) . '
'; + echo '' . yourls_s( 'Go back to the admin interface', yourls_admin_url('index.php') ) . '
'; + } } diff --git a/composer.json b/composer.json index 878baf42f..0dade84bb 100644 --- a/composer.json +++ b/composer.json @@ -16,21 +16,32 @@ "source": "https://github.com/YOURLS/YOURLS" }, "require": { - "php": ">=7.2", + "php": "^8.1", + "ext-dom": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-pcre": "*", "ext-pdo": "*", - "ozh/bookmarkletgen": "1.2", - "ozh/phpass": "1.3.0", - "rmccue/requests" : "1.8.0", - "pomo/pomo" : "1.4.1", - "geoip2/geoip2" : "2.10.0", - "aura/sql": "~3.", - "jakeasmith/http_build_url": "1.0.1", - "symfony/polyfill-mbstring": "1.15.0", + "ext-pdo_mysql": "*", + "ozh/bookmarkletgen": "^1.2", + "rmccue/requests" : "^2.0", + "pomo/pomo" : "^1.4", + "geoip2/geoip2" : "^2.10", + "aura/sql": "^6.0", + "jakeasmith/http_build_url": "^1.0", + "symfony/polyfill-mbstring": "^1.15", "symfony/polyfill-intl-idn": "^1.17", - "spatie/array-to-xml": "^2.14" + "spatie/array-to-xml": "^3.4" + }, + "require-dev": { + "ext-ctype": "*" }, "config": { - "vendor-dir": "includes/vendor" + "vendor-dir": "includes/vendor", + "platform": { + "php": "8.1.0" + }, + "platform-check": false }, "autoload": { "psr-4": { @@ -38,8 +49,15 @@ } }, "suggest": { + "ext-bcmath": "May be needed to read GeoIP database (or ext-gmp)", + "ext-curl": "Required for API usage", + "ext-gmp": "May be needed to read GeoIP database (or ext-bcmath)", + "ext-iconv": "For safer input handling", + "ext-json": "For faster API performance", "ext-mbstring": "For best performance", - "ext-curl": "Required for API usage" + "ext-openssl": "To fetch titles from HTTPS sites", + "ext-posix": "May be needed on certain PHP versions", + "ext-zlib": "For best performance" }, "scripts": { "post-update-cmd": "bash ./includes/vendor/build-script/yourls-build.sh ./includes/vendor" diff --git a/composer.lock b/composer.lock index 8bd14a671..c2030a098 100644 --- a/composer.lock +++ b/composer.lock @@ -4,29 +4,30 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "70ceb8b1bf58da7e97bc5988e9b04f97", + "content-hash": "9aaa57cfc41c85172bc72eb82b7ae94e", "packages": [ { "name": "aura/sql", - "version": "3.0.0", + "version": "6.0.0", "source": { "type": "git", "url": "https://github.com/auraphp/Aura.Sql.git", - "reference": "2be02d5dfd9fdee6df199de1a19572aa490bb744" + "reference": "8e2bb362e8953198df3682c9122e8b9edab5ff20" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/auraphp/Aura.Sql/zipball/2be02d5dfd9fdee6df199de1a19572aa490bb744", - "reference": "2be02d5dfd9fdee6df199de1a19572aa490bb744", + "url": "https://api.github.com/repos/auraphp/Aura.Sql/zipball/8e2bb362e8953198df3682c9122e8b9edab5ff20", + "reference": "8e2bb362e8953198df3682c9122e8b9edab5ff20", "shasum": "" }, "require": { - "php": ">=5.6.0", - "psr/log": "^1.0" + "ext-pdo": "*", + "php": "^8.4", + "psr/log": "^1.0 || ^2.0 || ^3.0" }, "require-dev": { "pds/skeleton": "~1.0", - "phpunit/phpunit": "~5.0" + "phpunit/phpunit": "^9.5" }, "type": "library", "autoload": { @@ -59,34 +60,34 @@ ], "support": { "issues": "https://github.com/auraphp/Aura.Sql/issues", - "source": "https://github.com/auraphp/Aura.Sql/tree/3.x" + "source": "https://github.com/auraphp/Aura.Sql/tree/6.0.0" }, - "time": "2018-06-11T12:57:42+00:00" + "time": "2025-01-22T06:43:21+00:00" }, { "name": "composer/ca-bundle", - "version": "1.2.9", + "version": "1.5.12", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "78a0e288fdcebf92aa2318a8d3656168da6ac1a5" + "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/78a0e288fdcebf92aa2318a8d3656168da6ac1a5", - "reference": "78a0e288fdcebf92aa2318a8d3656168da6ac1a5", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", + "reference": "00a2f4201641d5c53f7fc0195e6c8d9fcc321a78", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0 || ^8.0" + "php": "^7.2 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^0.12.55", - "psr/log": "^1.0", - "symfony/phpunit-bridge": "^4.2 || ^5", - "symfony/process": "^2.5 || ^3.0 || ^4.0 || ^5.0" + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^8 || ^9", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { @@ -121,7 +122,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.2.9" + "source": "https://github.com/composer/ca-bundle/tree/1.5.12" }, "funding": [ { @@ -131,37 +132,34 @@ { "url": "https://github.com/composer", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2021-01-12T12:10:35+00:00" + "time": "2026-05-19T11:26:22+00:00" }, { "name": "geoip2/geoip2", - "version": "v2.10.0", + "version": "v2.13.0", "source": { "type": "git", "url": "https://github.com/maxmind/GeoIP2-php.git", - "reference": "419557cd21d9fe039721a83490701a58c8ce784a" + "reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/419557cd21d9fe039721a83490701a58c8ce784a", - "reference": "419557cd21d9fe039721a83490701a58c8ce784a", + "url": "https://api.github.com/repos/maxmind/GeoIP2-php/zipball/6a41d8fbd6b90052bc34dff3b4252d0f88067b23", + "reference": "6a41d8fbd6b90052bc34dff3b4252d0f88067b23", "shasum": "" }, "require": { "ext-json": "*", - "maxmind-db/reader": "~1.5", - "maxmind/web-service-common": "~0.6", - "php": ">=5.6" + "maxmind-db/reader": "~1.8", + "maxmind/web-service-common": "~0.8", + "php": ">=7.2" }, "require-dev": { - "friendsofphp/php-cs-fixer": "2.*", - "phpunit/phpunit": "5.*", + "friendsofphp/php-cs-fixer": "3.*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "^8.0 || ^9.0", "squizlabs/php_codesniffer": "3.*" }, "type": "library", @@ -192,9 +190,9 @@ ], "support": { "issues": "https://github.com/maxmind/GeoIP2-php/issues", - "source": "https://github.com/maxmind/GeoIP2-php/tree/master" + "source": "https://github.com/maxmind/GeoIP2-php/tree/v2.13.0" }, - "time": "2019-12-12T18:48:39+00:00" + "time": "2022-08-05T20:32:58+00:00" }, { "name": "jakeasmith/http_build_url", @@ -235,35 +233,35 @@ }, { "name": "maxmind-db/reader", - "version": "v1.9.0", + "version": "v1.13.1", "source": { "type": "git", "url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git", - "reference": "9ee9ba9ee287b119e9f5a8e8dbfea0b49647cec4" + "reference": "2194f58d0f024ce923e685cdf92af3daf9951908" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/9ee9ba9ee287b119e9f5a8e8dbfea0b49647cec4", - "reference": "9ee9ba9ee287b119e9f5a8e8dbfea0b49647cec4", + "url": "https://api.github.com/repos/maxmind/MaxMind-DB-Reader-php/zipball/2194f58d0f024ce923e685cdf92af3daf9951908", + "reference": "2194f58d0f024ce923e685cdf92af3daf9951908", "shasum": "" }, "require": { "php": ">=7.2" }, "conflict": { - "ext-maxminddb": "<1.9.0,>=2.0.0" + "ext-maxminddb": "<1.11.1 || >=2.0.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "*", - "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpcov": ">=6.0.0", + "friendsofphp/php-cs-fixer": "3.*", + "phpstan/phpstan": "*", "phpunit/phpunit": ">=8.0.0,<10.0.0", - "squizlabs/php_codesniffer": "3.*" + "squizlabs/php_codesniffer": "4.*" }, "suggest": { "ext-bcmath": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", "ext-gmp": "bcmath or gmp is required for decoding larger integers with the pure PHP decoder", - "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups" + "ext-maxminddb": "A C-based database decoder that provides significantly faster lookups", + "maxmind-db/reader-ext": "C extension for significantly faster IP lookups (install via PIE: pie install maxmind-db/reader-ext)" }, "type": "library", "autoload": { @@ -293,34 +291,35 @@ ], "support": { "issues": "https://github.com/maxmind/MaxMind-DB-Reader-php/issues", - "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.9.0" + "source": "https://github.com/maxmind/MaxMind-DB-Reader-php/tree/v1.13.1" }, - "time": "2021-01-07T21:15:29+00:00" + "time": "2025-11-21T22:24:26+00:00" }, { "name": "maxmind/web-service-common", - "version": "v0.8.1", + "version": "v0.11.1", "source": { "type": "git", "url": "https://github.com/maxmind/web-service-common-php.git", - "reference": "32f274051c543fc865e5a84d3a2c703913641ea8" + "reference": "c309236b5a5555b96cf560089ec3cead12d845d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/32f274051c543fc865e5a84d3a2c703913641ea8", - "reference": "32f274051c543fc865e5a84d3a2c703913641ea8", + "url": "https://api.github.com/repos/maxmind/web-service-common-php/zipball/c309236b5a5555b96cf560089ec3cead12d845d2", + "reference": "c309236b5a5555b96cf560089ec3cead12d845d2", "shasum": "" }, "require": { "composer/ca-bundle": "^1.0.3", "ext-curl": "*", "ext-json": "*", - "php": ">=7.2" + "php": ">=8.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "2.*", - "phpunit/phpunit": "^8.0 || ^9.0", - "squizlabs/php_codesniffer": "3.*" + "friendsofphp/php-cs-fixer": "3.*", + "phpstan/phpstan": "*", + "phpunit/phpunit": "^10.0", + "squizlabs/php_codesniffer": "4.*" }, "type": "library", "autoload": { @@ -343,31 +342,31 @@ "homepage": "https://github.com/maxmind/web-service-common-php", "support": { "issues": "https://github.com/maxmind/web-service-common-php/issues", - "source": "https://github.com/maxmind/web-service-common-php/tree/v0.8.1" + "source": "https://github.com/maxmind/web-service-common-php/tree/v0.11.1" }, - "time": "2020-11-02T17:00:53+00:00" + "time": "2026-01-13T17:56:03+00:00" }, { "name": "ozh/bookmarkletgen", - "version": "1.2", + "version": "1.3", "source": { "type": "git", "url": "https://github.com/ozh/bookmarkletgen.git", - "reference": "3319b53c493a1474a03d1cc4e087617652284c20" + "reference": "7b462817642aa599c558a6376c0c0d7bf3ffc949" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ozh/bookmarkletgen/zipball/3319b53c493a1474a03d1cc4e087617652284c20", - "reference": "3319b53c493a1474a03d1cc4e087617652284c20", + "url": "https://api.github.com/repos/ozh/bookmarkletgen/zipball/7b462817642aa599c558a6376c0c0d7bf3ffc949", + "reference": "7b462817642aa599c558a6376c0c0d7bf3ffc949", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=8.0" }, "type": "library", "autoload": { - "psr-0": { - "Ozh\\Bookmarkletgen\\": "src/" + "psr-4": { + "Ozh\\Bookmarkletgen\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -388,81 +387,30 @@ ], "support": { "issues": "https://github.com/ozh/bookmarkletgen/issues", - "source": "https://github.com/ozh/bookmarkletgen/tree/master" - }, - "time": "2017-05-18T12:46:21+00:00" - }, - { - "name": "ozh/phpass", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/ozh/phpass.git", - "reference": "44149d1ee06ccbda397f08f69d32c59802e4ce43" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ozh/phpass/zipball/44149d1ee06ccbda397f08f69d32c59802e4ce43", - "reference": "44149d1ee06ccbda397f08f69d32c59802e4ce43", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": ">=4.0" - }, - "type": "library", - "autoload": { - "psr-0": { - "Ozh\\Phpass\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Public Domain" - ], - "authors": [ - { - "name": "Solar Designer", - "email": "solar@openwall.com", - "homepage": "http://openwall.com/phpass/" - } - ], - "description": "Portable PHP password hashing framework", - "homepage": "http://github.com/ozh/phpass/", - "keywords": [ - "blowfish", - "crypt", - "password", - "security" - ], - "support": { - "issues": "https://github.com/ozh/phpass/issues", - "source": "https://github.com/ozh/phpass/tree/1.3.0" + "source": "https://github.com/ozh/bookmarkletgen/tree/1.3" }, - "time": "2020-03-29T10:39:31+00:00" + "time": "2025-11-07T17:20:10+00:00" }, { "name": "pomo/pomo", - "version": "v1.4.1", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/LeoColomb/pomo.git", - "reference": "1594bd1f90c89a45ffc3da2ee6d5d582bfac7542" + "reference": "b1e53a997850496369634d574fa6b508091fc353" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/LeoColomb/pomo/zipball/1594bd1f90c89a45ffc3da2ee6d5d582bfac7542", - "reference": "1594bd1f90c89a45ffc3da2ee6d5d582bfac7542", + "url": "https://api.github.com/repos/LeoColomb/pomo/zipball/b1e53a997850496369634d574fa6b508091fc353", + "reference": "b1e53a997850496369634d574fa6b508091fc353", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": ">=4.0", - "squizlabs/php_codesniffer": "^3.0 || ^2.9.1" + "phpunit/phpunit": "^4.0 || ^7.0", + "squizlabs/php_codesniffer": "^3.0" }, "type": "library", "autoload": { @@ -486,7 +434,7 @@ "role": "Maintainer" } ], - "description": "Gettext library to translate with I18n", + "description": "Gettext library to translate with i18n", "homepage": "https://github.com/LeoColomb/pomo", "keywords": [ "gettext", @@ -497,36 +445,46 @@ ], "support": { "issues": "https://github.com/LeoColomb/pomo/issues", - "source": "https://github.com/LeoColomb/pomo/tree/master" + "source": "https://github.com/LeoColomb/pomo/tree/v1.5.0" }, - "time": "2018-12-20T14:55:38+00:00" + "funding": [ + { + "url": "https://github.com/LeoColomb", + "type": "github" + }, + { + "url": "https://www.patreon.com/LeoColomb", + "type": "patreon" + } + ], + "time": "2023-01-06T01:05:43+00:00" }, { "name": "psr/log", - "version": "1.1.3", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", - "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": ">=8.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "Psr/Log/" + "Psr\\Log\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -536,7 +494,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", @@ -547,42 +505,55 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/1.1.3" + "source": "https://github.com/php-fig/log/tree/3.0.2" }, - "time": "2020-03-23T09:12:05+00:00" + "time": "2024-09-11T13:17:53+00:00" }, { "name": "rmccue/requests", - "version": "v1.8.0", + "version": "v2.0.18", "source": { "type": "git", "url": "https://github.com/WordPress/Requests.git", - "reference": "afbe4790e4def03581c4a0963a1e8aa01f6030f1" + "reference": "2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/WordPress/Requests/zipball/afbe4790e4def03581c4a0963a1e8aa01f6030f1", - "reference": "afbe4790e4def03581c4a0963a1e8aa01f6030f1", + "url": "https://api.github.com/repos/WordPress/Requests/zipball/2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773", + "reference": "2e5b8434e0dd54b35bcf1e9a5b52ba2ad84cf773", "shasum": "" }, "require": { - "php": ">=5.2" + "ext-json": "*", + "php": ">=5.6" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7 || ^1.0", "php-parallel-lint/php-console-highlighter": "^0.5.0", - "php-parallel-lint/php-parallel-lint": "^1.3", - "phpcompatibility/php-compatibility": "^9.0", - "phpunit/phpunit": "^4.8 || ^5.7 || ^6.5 || ^7.5", - "requests/test-server": "dev-master", - "squizlabs/php_codesniffer": "^3.5", - "wp-coding-standards/wpcs": "^2.0" + "php-parallel-lint/php-parallel-lint": "^1.3.1", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "requests/test-server": "dev-main", + "squizlabs/php_codesniffer": "^3.6", + "wp-coding-standards/wpcs": "^2.0", + "yoast/phpunit-polyfills": "^1.1.5" + }, + "suggest": { + "art4/requests-psr18-adapter": "For using Requests as a PSR-18 HTTP Client", + "ext-curl": "For improved performance", + "ext-openssl": "For secure transport support", + "ext-zlib": "For improved performance when decompressing encoded streams" }, "type": "library", "autoload": { - "psr-0": { - "Requests": "library/" - } + "files": [ + "library/Deprecated.php" + ], + "psr-4": { + "WpOrg\\Requests\\": "src/" + }, + "classmap": [ + "library/Requests.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -591,11 +562,23 @@ "authors": [ { "name": "Ryan McCue", - "homepage": "http://ryanmccue.info" + "homepage": "https://rmccue.io/" + }, + { + "name": "Alain Schlesser", + "homepage": "https://github.com/schlessera" + }, + { + "name": "Juliette Reinders Folmer", + "homepage": "https://github.com/jrfnl" + }, + { + "name": "Contributors", + "homepage": "https://github.com/WordPress/Requests/graphs/contributors" } ], "description": "A HTTP library written in PHP, for human beings.", - "homepage": "http://github.com/WordPress/Requests", + "homepage": "https://requests.ryanmccue.info/", "keywords": [ "curl", "fsockopen", @@ -606,35 +589,41 @@ "sockets" ], "support": { + "docs": "https://requests.ryanmccue.info/", "issues": "https://github.com/WordPress/Requests/issues", - "source": "https://github.com/WordPress/Requests/tree/v1.8.0" + "source": "https://github.com/WordPress/Requests" }, - "time": "2021-04-27T11:05:25+00:00" + "time": "2026-04-30T00:41:23+00:00" }, { "name": "spatie/array-to-xml", - "version": "2.15.0", + "version": "3.4.4", "source": { "type": "git", "url": "https://github.com/spatie/array-to-xml.git", - "reference": "1795afad4e5a1f4b7af2e0e09802550eaa00a6f8" + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/1795afad4e5a1f4b7af2e0e09802550eaa00a6f8", - "reference": "1795afad4e5a1f4b7af2e0e09802550eaa00a6f8", + "url": "https://api.github.com/repos/spatie/array-to-xml/zipball/88b2f3852a922dd73177a68938f8eb2ec70c7224", + "reference": "88b2f3852a922dd73177a68938f8eb2ec70c7224", "shasum": "" }, "require": { "ext-dom": "*", - "php": "^7.2" + "php": "^8.0" }, "require-dev": { - "mockery/mockery": "^1.0", - "phpunit/phpunit": "^8.0", - "spatie/phpunit-snapshot-assertions": "^2.0" + "mockery/mockery": "^1.2", + "pestphp/pest": "^1.21", + "spatie/pest-plugin-snapshots": "^1.1" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, "autoload": { "psr-4": { "Spatie\\ArrayToXml\\": "src" @@ -648,7 +637,7 @@ { "name": "Freek Van der Herten", "email": "freek@spatie.be", - "homepage": "https://murze.be", + "homepage": "https://freek.dev", "role": "Developer" } ], @@ -660,50 +649,55 @@ "xml" ], "support": { - "issues": "https://github.com/spatie/array-to-xml/issues", - "source": "https://github.com/spatie/array-to-xml/tree/2.15.0" + "source": "https://github.com/spatie/array-to-xml/tree/3.4.4" }, - "time": "2020-10-29T18:11:03+00:00" + "funding": [ + { + "url": "https://spatie.be/open-source/support-us", + "type": "custom" + }, + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-12-15T09:00:41+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.22.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44" + "reference": "dc21118016c039a66235cf93d96b435ffb282412" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44", - "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/dc21118016c039a66235cf93d96b435ffb282412", + "reference": "dc21118016c039a66235cf93d96b435ffb282412", "shasum": "" }, "require": { - "php": ">=7.1", - "symfony/polyfill-intl-normalizer": "^1.10", - "symfony/polyfill-php72": "^1.10" + "php": ">=7.2", + "symfony/polyfill-intl-normalizer": "^1.10" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.22-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Idn\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -734,7 +728,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.38.1" }, "funding": [ { @@ -745,50 +739,51 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2026-05-25T15:22:23+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.22.0", + "version": "v1.38.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba" + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/6e971c891537eb617a00bb07a43d182a6915faba", - "reference": "6e971c891537eb617a00bb07a43d182a6915faba", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/2d446c214bdbe5b71bde5011b060a05fece3ae6b", + "reference": "2d446c214bdbe5b71bde5011b060a05fece3ae6b", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "suggest": { "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.22-dev" - }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, "files": [ "bootstrap.php" ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, "classmap": [ "Resources/stubs" ] @@ -818,7 +813,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.22.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.38.0" }, "funding": [ { @@ -829,46 +824,55 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2021-01-07T17:09:11+00:00" + "time": "2026-05-25T13:48:31+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "version": "v1.38.1", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/14c5439eec4ccff081ac14eca2dc57feb2a66d92", + "reference": "14c5439eec4ccff081ac14eca2dc57feb2a66d92", "shasum": "" }, "require": { - "php": ">=5.3.3" + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" }, "suggest": { "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.15-dev" + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, "files": [ "bootstrap.php" - ] + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -894,7 +898,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.15.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.38.1" }, "funding": [ { @@ -906,79 +910,7 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2020-03-09T19:04:49+00:00" - }, - { - "name": "symfony/polyfill-php72", - "version": "v1.22.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", - "reference": "cc6e6f9b39fe8075b3dabfbaf5b5f645ae1340c9", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.22-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.22.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", + "url": "https://github.com/nicolas-grekas", "type": "github" }, { @@ -986,19 +918,29 @@ "type": "tidelift" } ], - "time": "2021-01-07T16:49:33+00:00" + "time": "2026-05-26T12:51:13+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=7.2", - "ext-pdo": "*" + "php": "^8.1", + "ext-dom": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-pcre": "*", + "ext-pdo": "*", + "ext-pdo_mysql": "*" + }, + "platform-dev": { + "ext-ctype": "*" + }, + "platform-overrides": { + "php": "8.1.0" }, - "platform-dev": [], - "plugin-api-version": "2.0.0" + "plugin-api-version": "2.9.0" } diff --git a/css/style.css b/css/style.css index d7f50d971..d475bcafa 100644 --- a/css/style.css +++ b/css/style.css @@ -18,8 +18,6 @@ body { border-right:3px solid #2a85b3; border-bottom:3px solid #2a85b3; border-top:3px solid #2a85b3; - -moz-border-radius:20px; - -webkit-border-radius:20px; border-radius:20px; } .hide-if-no-js {display: none;} @@ -34,7 +32,7 @@ a, a:link, a:active, a:visited { a:hover { text-decoration: underline; } -h1 {height:50px;margin:0;float:right;max-width:500px;} +h1 {margin:0;float:right;max-width:500px;} h1 a {text-align:right;font-size:20px;float:right;} h1 a, h1 a:link, h1 a:active, h1 a:visited {color:#2a85b3} h1 a:hover{text-decoration:none;} @@ -68,8 +66,6 @@ tt { } input, textarea { - -moz-border-radius:3px; - -webkit-border-radius:3px; border-radius:3px; } Input.text, select, textarea { @@ -129,8 +125,11 @@ tr.edit-row td { td.url small a{ color:#bbc; } -body.desktop td.actions input,body.desktop td.actions a { - visibility:hidden; +/* Hide buttons visually but keep them accessible to screen readers */ +body.desktop td.actions input, body.desktop td.actions a { + opacity: 0; + pointer-events: none; + transition: opacity 0.2s ease; } td.timestamp span.timestamp { display:none; @@ -138,8 +137,20 @@ td.timestamp span.timestamp { td.actions input.disabled, td.actions input.loading { visibility:visible; } +/* Show buttons on row hover */ tr:hover td.actions input, tr:hover td.actions a { - visibility:visible; + opacity: 1; + pointer-events: auto; +} +/* Show buttons when any button in the actions cell receives focus */ +td.actions:focus-within input, td.actions:focus-within a { + opacity: 1; + pointer-events: auto; +} +/* Show all buttons when any element in the row receives focus (for keyboard nav through the entire row) */ +tr:focus-within td.actions input, tr:focus-within td.actions a { + opacity: 1; + pointer-events: auto; } td.actions .button { font-family: Verdana, Arial; @@ -148,8 +159,6 @@ td.actions .button { font-weight: bold; background-color: #FFFFFF; border: 1px solid #88c0eb; - -moz-border-radius:3px; - -webkit-border-radius:3px; border-radius:3px; cursor:pointer; height:22px; @@ -203,7 +212,7 @@ td.actions .button_stats { background:#efe; } #login { - width: 300px; + max-width: 300px; margin: 200px auto 0px auto; } #login p{ @@ -240,8 +249,6 @@ td.actions .button_stats { } a.bookmarklet { border:2px solid #2a85b3; - -moz-border-radius:3px; - -webkit-border-radius:3px; border-radius:3px; padding:5px 5px 5px 20px; background:#eef url(../images/favicon.svg) 2px center no-repeat; @@ -262,14 +269,8 @@ a.bookmarklet:hover { background:white; margin:0 auto; max-width:950px; - -moz-border-radius:10px; - -webkit-border-radius:10px; border-radius:10px; border:2px solid #2a85b3; - -moz-border-radius-bottomleft:30px; - -moz-border-radius-bottomright:30px; - -webkit-border-bottom-left-radius:25px; - -webkit-border-bottom-right-radius:25px; border-bottom-left-radius:25px; border-bottom-right-radius:25px; } @@ -281,16 +282,14 @@ a.bookmarklet:hover { .notice { border:1px solid #2a85b3; background: #F3FAFD; - -moz-border-radius:6px; - -webkit-border-radius:6px; border-radius:6px; width:70%; margin-left:15%; padding-left:10px; margin-bottom:5px; + clear: both; } - .jquery-notify-bar { width:100%; position:fixed; @@ -305,11 +304,7 @@ a.bookmarklet:hover { padding:20px 0px; border-bottom:1px solid #bbb; filter:alpha(opacity=95); - -moz-opacity:0.95; - -khtml-opacity:0.95; opacity:0.95; - -moz-box-shadow: 0 1px 5px rgba(0,0,0,0.5); - -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); text-shadow: 0 1px 1px rgba(0,0,0,0.1); } .jquery-notify-bar.error ,.jquery-notify-bar.fail { @@ -335,3 +330,59 @@ a.bookmarklet:hover { tr.plugin.active a{ font-weight:bolder;} body.desktop tr.plugin td.plugin_desc small{ visibility:hidden;} tr:hover.plugin td.plugin_desc small{ visibility:visible;} + +#delete-confirm-dialog { + background-color: #ffffff; + width: 50em; + height: 19em; + padding: 0px; + border: 3px solid #2a85b3; + border-radius: 20px; +} +#delete-confirm-dialog > div[name="dialog_title"] { + background-color: #c7e7ff; + font-size: 20px; + color: #026090; + text-align: center; + padding-top: 0.5em; + padding-bottom: 0.5em; + border-start-start-radius: 17px 17px; + border-start-end-radius: 17px 17px; +} +#delete-confirm-dialog div.confirm-message { + background-color: #ffffff; + width: calc(50em - 4em); + height: calc(20em - 50px - 5em - 2em + 6px); + float: none; + text-align: left; + padding: 1em 2em; + overflow: hidden; +} +#delete-confirm-dialog div.confirm-message ul { + border-left: 5px solid #026090; + list-style-type: none; + padding: 0 1em; +} +#delete-confirm-dialog div.confirm-message ul li { + margin-bottom: 0.5em; +} +#delete-confirm-dialog div.confirm-message ul li span { + border: 1px solid #c7e7ff; + border-radius: 3px; + padding: 1px 5px; + color:#333; +} +#delete-confirm-dialog div.button-group { + background-color: #e3f3ff; + width: calc(50em - 4em); + height: 2em; + float: none; + text-align: right; + padding: 1em 2em; + border-end-start-radius: 17px 17px; + border-end-end-radius: 17px 17px; +} +#delete-confirm-dialog::backdrop { + background-color: #666; + opacity: 80%; +} diff --git a/includes/Config/Config.php b/includes/Config/Config.php index 5958d70fe..6f50194e4 100644 --- a/includes/Config/Config.php +++ b/includes/Config/Config.php @@ -11,50 +11,50 @@ class Config { /** - * @param string + * @var string */ - protected $root; + protected string $root; /** - * @param mixed + * @var string */ - protected $config; + protected string $config; /** * @since 1.7.3 - * @param mixed $config Optional user defined config path + * @param string $config Optional user defined config path */ - public function __construct($config = false) { - $this->set_root( $this->fix_win32_path( dirname( dirname( __DIR__ ) ) ) ); + public function __construct(string $config = '') { + $this->set_root( $this->fix_win32_path(dirname(__DIR__, 2)) ); $this->set_config($config); } /** - * Convert antislashes to slashes + * Convert backslashes to slashes * * @since 1.7.3 - * @param string $path + * @param string $path * @return string path with \ converted to / */ - public function fix_win32_path($path) { + public function fix_win32_path(string $path): string { return str_replace('\\', '/', $path); } /** * @since 1.7.3 - * @param string path to config file + * @param string $config path to config file * @return void */ - public function set_config($config) { + public function set_config(string $config): void { $this->config = $config; } /** * @since 1.7.3 - * @param string path to YOURLS root directory + * @param string $root path to YOURLS root directory * @return void */ - public function set_root($root) { + public function set_root(string $root): void { $this->root = $root; } @@ -65,7 +65,7 @@ public function set_root($root) { * @return string path to found config file * @throws ConfigException */ - public function find_config() { + public function find_config(): string { $config = $this->fix_win32_path($this->config); @@ -99,7 +99,7 @@ public function find_config() { * @return void * @throws ConfigException */ - public function define_core_constants() { + public function define_core_constants(): void { // Check minimal config job has been properly done $must_haves = array('YOURLS_DB_USER', 'YOURLS_DB_PASS', 'YOURLS_DB_NAME', 'YOURLS_DB_HOST', 'YOURLS_DB_PREFIX', 'YOURLS_SITE'); foreach($must_haves as $must_have) { @@ -177,40 +177,11 @@ public function define_core_constants() { if (!defined( 'YOURLS_DB_TABLE_LOG' )) define( 'YOURLS_DB_TABLE_LOG', YOURLS_DB_PREFIX.'log' ); - // minimum delay in sec before a same IP can add another URL. Note: logged in users are not throttled down. - if (!defined( 'YOURLS_FLOOD_DELAY_SECONDS' )) - define( 'YOURLS_FLOOD_DELAY_SECONDS', 15 ); - - // comma separated list of IPs that can bypass flood check. - if (!defined( 'YOURLS_FLOOD_IP_WHITELIST' )) - define( 'YOURLS_FLOOD_IP_WHITELIST', '' ); - - // life span of an auth cookie in seconds (60*60*24*7 = 7 days) - if (!defined( 'YOURLS_COOKIE_LIFE' )) - define( 'YOURLS_COOKIE_LIFE', 60*60*24*7 ); - - // life span of a nonce in seconds - if (!defined( 'YOURLS_NONCE_LIFE' )) - define( 'YOURLS_NONCE_LIFE', 43200 ); // 3600 * 12 - - // if set to true, disable stat logging (no use for it, too busy servers, ...) - if (!defined( 'YOURLS_NOSTATS' )) - define( 'YOURLS_NOSTATS', false ); - - // if set to true, force https:// in the admin area - if (!defined( 'YOURLS_ADMIN_SSL' )) - define( 'YOURLS_ADMIN_SSL', false ); - - // if set to true, verbose debug infos. Will break things. Don't enable. - if (!defined( 'YOURLS_DEBUG' )) - define( 'YOURLS_DEBUG', false ); - - // Error reporting - if (defined( 'YOURLS_DEBUG' ) && YOURLS_DEBUG == true ) { - error_reporting( -1 ); - } else { - error_reporting( E_ERROR | E_PARSE ); + // if set to true, verbose debug infos + if (!defined( 'YOURLS_DEBUG' )) { + define('YOURLS_DEBUG', false); } + } } diff --git a/includes/Config/Init.php b/includes/Config/Init.php index dce118d01..d97d9ae2f 100644 --- a/includes/Config/Init.php +++ b/includes/Config/Init.php @@ -9,7 +9,7 @@ class Init { /** - * @param InitDefaults + * @var InitDefaults */ protected $actions; @@ -32,11 +32,6 @@ public function __construct(InitDefaults $actions) { date_default_timezone_set( 'UTC' ); } - // Load locale - if ($actions->load_default_textdomain === true) { - yourls_load_default_textdomain(); - } - // Check if we are in maintenance mode - if yes, it will die here. if ($actions->check_maintenance_mode === true) { yourls_check_maintenance_mode(); @@ -57,7 +52,7 @@ public function __construct(InitDefaults $actions) { $this->include_db_files(); } - // Allow early inclusion of a cache layer + // Allow early and unconditional inclusion of custom code if ($actions->include_cache === true) { $this->include_cache_files(); } @@ -87,6 +82,7 @@ public function __construct(InitDefaults $actions) { if (!yourls_is_installed() && !yourls_is_installing()) { yourls_no_cache_headers(); yourls_redirect( yourls_admin_url('install.php'), 307 ); + exit(); } } @@ -95,6 +91,7 @@ public function __construct(InitDefaults $actions) { if (!yourls_is_upgrading() && !yourls_is_installing() && yourls_upgrade_is_needed()) { yourls_no_cache_headers(); yourls_redirect( yourls_admin_url('upgrade.php'), 307 ); + exit(); } } @@ -108,6 +105,11 @@ public function __construct(InitDefaults $actions) { yourls_do_action( 'plugins_loaded' ); } + // Load locale + if ($actions->load_default_textdomain === true) { + yourls_load_default_textdomain(); + } + // Is there a new version of YOURLS ? if ($actions->check_new_version === true) { if (yourls_is_installed() && !yourls_is_upgrading()) { @@ -143,22 +145,41 @@ public function redirect_ssl_if_needed() { * @return void */ public function include_db_files() { - // Allow drop-in replacement for the DB engine - if (file_exists(YOURLS_USERDIR.'/db.php')) { - require_once YOURLS_USERDIR.'/db.php'; - } else { - require_once YOURLS_INC.'/class-mysql.php'; + // Attempt to open drop-in replacement for the DB engine else default to core engine + $file = YOURLS_USERDIR . '/db.php'; + $attempt = false; + if(file_exists($file)) { + $attempt = yourls_include_file_sandbox( $file ); + // Check if we have an error to display + if ( is_string( $attempt ) ) { + yourls_add_notice( $attempt ); + } + } + + // Fallback to core DB engine + if ( $attempt !== true ) { + require_once YOURLS_INC . '/class-mysql.php'; yourls_db_connect(); } } /** + * Include custom extension file. + * + * "Cache" stands for "Custom Additional Code for Hazardous Extensions". + * * @since 1.7.3 * @return void */ public function include_cache_files() { - if (file_exists(YOURLS_USERDIR.'/cache.php')) { - require_once YOURLS_USERDIR.'/cache.php'; + $file = YOURLS_USERDIR . '/cache.php'; + $attempt = false; + if(file_exists($file)) { + $attempt = yourls_include_file_sandbox($file); + // Check if we have an error to display + if (is_string($attempt)) { + yourls_add_notice($attempt); + } } } @@ -185,12 +206,8 @@ public function include_core_functions() { require_once YOURLS_INC.'/functions-infos.php'; require_once YOURLS_INC.'/functions-deprecated.php'; require_once YOURLS_INC.'/functions-auth.php'; - - // Load install & upgrade functions if needed - if ($this->actions->include_install_upgrade_funcs === true) { - require_once YOURLS_INC.'/functions-upgrade.php'; - require_once YOURLS_INC.'/functions-install.php'; - } + require_once YOURLS_INC.'/functions-upgrade.php'; + require_once YOURLS_INC.'/functions-install.php'; } } diff --git a/includes/Config/InitDefaults.php b/includes/Config/InitDefaults.php index 7792e0113..791650c9d 100644 --- a/includes/Config/InitDefaults.php +++ b/includes/Config/InitDefaults.php @@ -20,12 +20,6 @@ class InitDefaults { */ public $include_core_funcs = true; - /** - * Whether to include auth function files - * @var bool - */ - public $include_install_upgrade_funcs = false; // by default do not load - /** * Whether to set default time zone * @var bool diff --git a/includes/Database/Logger.php b/includes/Database/Logger.php index 6f171b575..56bc087e8 100644 --- a/includes/Database/Logger.php +++ b/includes/Database/Logger.php @@ -47,11 +47,16 @@ class Logger extends AbstractLogger { * ) * See finish() in Aura\Sql\Profiler\Profiler * - * @return null + * @return void */ - public function log($level, $message, array $context = []) { + public function log($level, string|\Stringable $message, array $context = []): void { // if it's an internal SQL query, format the message, otherwise store a string if($level === 'query') { + // Get the real function name that called the query (not just "perform") + $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 6); + // We should have at index 5 "fetchSomething" (eg fetchAll, fetchOne, etc), otherwise make it "perform" + $function = $backtrace[5]['function'] ?? 'perform'; + $context['function'] = str_starts_with($function, 'fetch') ? $function : 'perform'; $this->messages[] = sprintf( 'SQL %s: %s (%s s)', $context['function'], diff --git a/includes/Database/Options.php b/includes/Database/Options.php index 2eebb641b..f175f08be 100644 --- a/includes/Database/Options.php +++ b/includes/Database/Options.php @@ -75,7 +75,7 @@ public function get_all_options() { $this->ydb->set_option($name, yourls_maybe_unserialize($value)); } - yourls_apply_filter('get_all_options', 'deprecated'); + yourls_do_action('get_all_options', $options); return true; } @@ -172,7 +172,7 @@ public function update($name, $newvalue) { // Cache option value to save a DB query if needed later $this->ydb->set_option($name, $newvalue); - yourls_do_action( 'update_option', $name, $oldvalue, $newvalue ); + yourls_do_action( 'update_option', $name, $oldvalue, $newvalue ); return true; } diff --git a/includes/Database/Profiler.php b/includes/Database/Profiler.php index 2b700b987..ea9e4d222 100644 --- a/includes/Database/Profiler.php +++ b/includes/Database/Profiler.php @@ -21,9 +21,9 @@ class Profiler extends \Aura\Sql\Profiler\Profiler { * * @param string $statement The statement being profiled, if any. * @param array $values The values bound to the statement, if any. - * @return null + * @return void */ - public function finish($statement = null, array $values = []) + public function finish(?string $statement = null, array $values = []): void { if (! $this->active) { return; diff --git a/includes/Database/YDB.php b/includes/Database/YDB.php index 30dac4355..6be405e1d 100644 --- a/includes/Database/YDB.php +++ b/includes/Database/YDB.php @@ -1,22 +1,21 @@ option, or $ydb->set_option(), use yourls_*_options() functions instead). + * function wrappers (e.g. don't use $ydb->option, or $ydb->set_option(), use yourls_*_options() functions instead). * * @since 1.7.3 */ namespace YOURLS\Database; -use \Aura\Sql\ExtendedPdo; -use \YOURLS\Database\Profiler; -use \YOURLS\Database\Logger; +use Aura\Sql\ExtendedPdo; use PDO; +use PDOStatement; class YDB extends ExtendedPdo { @@ -24,69 +23,74 @@ class YDB extends ExtendedPdo { * Debug mode, default false * @var bool */ - protected $debug = false; + protected bool $debug = false; /** * Page context (ie "infos", "bookmark", "plugins"...) * @var string */ - protected $context = ''; + protected string $context = ''; /** - * Information related to a short URL keyword (eg timestamp, long URL, ...) + * Information related to a short URL keyword (e.g. timestamp, long URL, ...) * * @var array * */ - protected $infos = []; + protected array $infos = []; /** * Is YOURLS installed and ready to run? * @var bool */ - protected $installed = false; + protected bool $installed = false; /** * Options * @var array */ - protected $option = []; + protected array $option = []; /** - * Plugin admin pages informations + * Plugin admin pages information * @var array */ - protected $plugin_pages = []; + protected array $plugin_pages = []; /** - * Plugin informations + * Plugin information * @var array */ - protected $plugins = []; + protected array $plugins = []; /** * Are we emulating prepare statements ? * @var bool */ - protected $is_emulate_prepare; + protected bool $is_emulate_prepare; + + /** + * Bypass shunt filter? See fetch_wrapper() + * @var bool + */ + private bool $bypass_shunt_filter = false; /** * @since 1.7.3 - * @param string $dsn The data source name - * @param string $user The username - * @param string $pass The password - * @param array $options Driver-specific options - * @param array $attributes Attributes to set after a connection + * @param string $dsn The data source name + * @param string $user The username + * @param string $pass The password + * @param array $options Driver-specific options */ - public function __construct($dsn, $user, $pass, $options, $attributes) { - parent::__construct($dsn, $user, $pass, $options, $attributes); + public function __construct($dsn, $user, $pass, $options) { + parent::__construct($dsn, $user, $pass, $options); } /** * Init everything needed * * Everything we need to set up is done here in init(), not in the constructor, so even - * when the connection fails (eg config error or DB dead), the constructor has worked + * when the connection fails (e.g. config error or DB dead), the constructor has worked, * and we have a $ydb object properly instantiated (and for instance yourls_die() can * correctly die, even if using $ydb methods) * @@ -140,7 +144,8 @@ public function get_emulate_state() { */ public function connect_to_DB() { try { - $this->connect(); + list($dsn, $_user, $_pwd, $_opt, $_queries) = $this->args; + $this->connect($dsn); } catch ( \Exception $e ) { $this->dead_or_error($e); } @@ -157,24 +162,26 @@ public function connect_to_DB() { */ public function dead_or_error(\Exception $exception) { // Use any /user/db_error.php file - if( file_exists( YOURLS_USERDIR . '/db_error.php' ) ) { - include_once( YOURLS_USERDIR . '/db_error.php' ); - die(); + $file = YOURLS_USERDIR . '/db_error.php'; + if(file_exists($file)) { + if(yourls_include_file_sandbox( $file ) === true) { + die(); + } } $message = yourls__( 'Incorrect DB config, or could not connect to DB' ); $message .= '';
+ $message .= yourls_s( 'Click here to dismiss this message for one week.', '?dismiss=md5warning' );
+
+ yourls_add_notice( $message );
+ }
+ } else {
+ // No md5 password, remove flag from DB if any.
+ if( yourls_get_option( 'defer_md5_warning' ) ) {
+ yourls_delete_option('defer_md5_warning');
+ }
+ }
+
}
diff --git a/includes/class-mysql.php b/includes/class-mysql.php
index d2ca2958e..6443636c6 100644
--- a/includes/class-mysql.php
+++ b/includes/class-mysql.php
@@ -4,8 +4,10 @@
* Connect to DB
*
* @since 1.0
+ * @param string $context Optional context. Default: ''. See yourls_get_db()
+ * @return \YOURLS\Database\YDB
*/
-function yourls_db_connect() {
+function yourls_db_connect($context = '') {
global $ydb;
if ( !defined( 'YOURLS_DB_USER' )
@@ -25,12 +27,12 @@ function yourls_db_connect() {
yourls_do_action( 'set_DB_driver', 'deprecated' );
// Get custom port if any
- if ( false !== strpos( $dbhost, ':' ) ) {
+ if (str_contains($dbhost, ':')) {
list( $dbhost, $dbport ) = explode( ':', $dbhost );
$dbhost = sprintf( '%1$s;port=%2$d', $dbhost, $dbport );
}
- $charset = yourls_apply_filter( 'db_connect_charset', 'utf8mb4' );
+ $charset = yourls_apply_filter( 'db_connect_charset', 'utf8mb4', $context );
/**
* Data Source Name (dsn) used to connect the DB
@@ -41,7 +43,7 @@ function yourls_db_connect() {
* 'pgsql:host=192.168.13.37;port=5432;dbname=omgwtf'
*/
$dsn = sprintf( 'mysql:host=%s;dbname=%s;charset=%s', $dbhost, $dbname, $charset );
- $dsn = yourls_apply_filter( 'db_connect_custom_dsn', $dsn );
+ $dsn = yourls_apply_filter( 'db_connect_custom_dsn', $dsn, $context );
/**
* PDO driver options and attributes
@@ -51,14 +53,14 @@ function yourls_db_connect() {
* The driver options are passed to the PDO constructor, eg array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)
* The attribute options are then set in a foreach($attr as $k=>$v){$db->setAttribute($k, $v)} loop
*/
- $driver_options = yourls_apply_filter( 'db_connect_driver_option', [] ); // driver options as key-value pairs
- $attributes = yourls_apply_filter( 'db_connect_attributes', [] ); // attributes as key-value pairs
+ $driver_options = yourls_apply_filter( 'db_connect_driver_option', [], $context ); // driver options as key-value pairs
+ $attributes = yourls_apply_filter( 'db_connect_attributes', [], $context ); // attributes as key-value pairs
$ydb = new \YOURLS\Database\YDB( $dsn, $user, $pass, $driver_options, $attributes );
$ydb->init();
// Past this point, we're connected
- yourls_debug_log( sprintf( 'Connected to database %s on %s ', $dbname, $dbhost ) );
+ yourls_debug_log( 'Connected to ' . $dsn );
yourls_debug_mode( YOURLS_DEBUG );
@@ -66,7 +68,7 @@ function yourls_db_connect() {
}
/**
- * Helper function : return instance of the DB
+ * Helper function: return instance of the DB
*
* Instead of:
* global $ydb;
@@ -75,18 +77,46 @@ function yourls_db_connect() {
* yourls_get_db()->do_stuff()
*
* @since 1.7.10
+ * @param string $context Optional context. Default: ''.
+ * If not provided, the function will trigger a notice to encourage developers to provide a context while not
+ * breaking existing code. A context is a string describing the operation for which the DB is requested.
+ * Use a naming schema starting with a prefix describing the operation, followed by a short description:
+ * - Prefix should be either "read-" or "write-", as follows:
+ * * "read-" for operations that only read from the DB (eg get_keyword_infos)
+ * * "write-" for operations that write to the DB (eg insert_link_in_db)
+ * - The description should be lowercase, words separated with underscores, eg "insert_link_in_db".
+ * Examples:
+ * - read-fetch_keyword
+ * - write-insert_link_in_db
* @return \YOURLS\Database\YDB
*/
-function yourls_get_db() {
+function yourls_get_db($context = '') {
// Allow plugins to short-circuit the whole function
- $pre = yourls_apply_filter( 'shunt_get_db', false );
- if ( false !== $pre ) {
+ $pre = yourls_apply_filter( 'shunt_get_db', yourls_shunt_default(), $context );
+ if ( yourls_shunt_default() !== $pre ) {
return $pre;
}
+ // Validate context and raise notice if missing or malformed
+ if ($context == '' || !preg_match('/^(read|write)-[a-z0-9_]+$/', $context)) {
+ $db = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
+ $file = $db[0]['file'];
+ $line = $db[0]['line'];
+
+ if ($context == '') {
+ $msg = 'Undefined yourls_get_db() context';
+ } else {
+ $msg = 'Improperly formatted yourls_get_db() context ("' . $context . '")';
+ }
+
+ trigger_error( $msg . ' at ' . $file . ':' . $line .'', E_USER_NOTICE );
+ }
+
+ yourls_do_action( 'get_db_action', $context );
+
global $ydb;
- $ydb = ( isset( $ydb ) ) ? $ydb : yourls_db_connect();
- return yourls_apply_filter('get_db', $ydb);
+ $ydb = ( isset( $ydb ) ) ? $ydb : yourls_db_connect($context);
+ return yourls_apply_filter('get_db', $ydb, $context);
}
/**
@@ -101,6 +131,7 @@ function yourls_get_db() {
*
* @since 1.7.10
* @param mixed $db Either a \YOURLS\Database\YDB instance, or anything. If null, the function will unset $ydb
+ * @return void
*/
function yourls_set_db($db) {
global $ydb;
diff --git a/includes/functions-api.php b/includes/functions-api.php
index baf3cb10e..bd9723758 100644
--- a/includes/functions-api.php
+++ b/includes/functions-api.php
@@ -15,13 +15,13 @@
* @return array Result of API call
*/
function yourls_api_action_shorturl() {
- $url = ( isset( $_REQUEST['url'] ) ? $_REQUEST['url'] : '' );
- $keyword = ( isset( $_REQUEST['keyword'] ) ? $_REQUEST['keyword'] : '' );
- $title = ( isset( $_REQUEST['title'] ) ? $_REQUEST['title'] : '' );
- $return = yourls_add_new_link( $url, $keyword, $title );
- $return['simple'] = ( isset( $return['shorturl'] ) ? $return['shorturl'] : '' ); // This one will be used in case output mode is 'simple'
- unset( $return['html'] ); // in API mode, no need for our internal HTML output
- return yourls_apply_filter( 'api_result_shorturl', $return );
+ $url = ( isset( $_REQUEST['url'] ) ? $_REQUEST['url'] : '' );
+ $keyword = ( isset( $_REQUEST['keyword'] ) ? $_REQUEST['keyword'] : '' );
+ $title = ( isset( $_REQUEST['title'] ) ? $_REQUEST['title'] : '' );
+ $return = yourls_add_new_link( $url, $keyword, $title );
+ $return['simple'] = ( isset( $return['shorturl'] ) ? $return['shorturl'] : '' ); // This one will be used in case output mode is 'simple'
+ unset( $return['html'] ); // in API mode, no need for our internal HTML output
+ return yourls_apply_filter( 'api_result_shorturl', $return );
}
/**
@@ -31,10 +31,10 @@ function yourls_api_action_shorturl() {
* @return array Result of API call
*/
function yourls_api_action_stats() {
- $filter = ( isset( $_REQUEST['filter'] ) ? $_REQUEST['filter'] : '' );
- $limit = ( isset( $_REQUEST['limit'] ) ? $_REQUEST['limit'] : '' );
- $start = ( isset( $_REQUEST['start'] ) ? $_REQUEST['start'] : '' );
- return yourls_apply_filter( 'api_result_stats', yourls_api_stats( $filter, $limit, $start ) );
+ $filter = ( isset( $_REQUEST['filter'] ) ? $_REQUEST['filter'] : '' );
+ $limit = ( isset( $_REQUEST['limit'] ) ? $_REQUEST['limit'] : '' );
+ $start = ( isset( $_REQUEST['start'] ) ? $_REQUEST['start'] : '' );
+ return yourls_apply_filter( 'api_result_stats', yourls_api_stats( $filter, $limit, $start ) );
}
/**
@@ -44,7 +44,7 @@ function yourls_api_action_stats() {
* @return array Result of API call
*/
function yourls_api_action_db_stats() {
- return yourls_apply_filter( 'api_result_db_stats', yourls_api_db_stats() );
+ return yourls_apply_filter( 'api_result_db_stats', yourls_api_db_stats() );
}
/**
@@ -54,8 +54,8 @@ function yourls_api_action_db_stats() {
* @return array Result of API call
*/
function yourls_api_action_url_stats() {
- $shorturl = ( isset( $_REQUEST['shorturl'] ) ? $_REQUEST['shorturl'] : '' );
- return yourls_apply_filter( 'api_result_url_stats', yourls_api_url_stats( $shorturl ) );
+ $shorturl = ( isset( $_REQUEST['shorturl'] ) ? $_REQUEST['shorturl'] : '' );
+ return yourls_apply_filter( 'api_result_url_stats', yourls_api_url_stats( $shorturl ) );
}
/**
@@ -65,8 +65,8 @@ function yourls_api_action_url_stats() {
* @return array Result of API call
*/
function yourls_api_action_expand() {
- $shorturl = ( isset( $_REQUEST['shorturl'] ) ? $_REQUEST['shorturl'] : '' );
- return yourls_apply_filter( 'api_result_expand', yourls_api_expand( $shorturl ) );
+ $shorturl = ( isset( $_REQUEST['shorturl'] ) ? $_REQUEST['shorturl'] : '' );
+ return yourls_apply_filter( 'api_result_expand', yourls_api_expand( $shorturl ) );
}
/**
@@ -76,10 +76,10 @@ function yourls_api_action_expand() {
* @return array Result of API call
*/
function yourls_api_action_version() {
- $return['version'] = $return['simple'] = YOURLS_VERSION;
- if( isset( $_REQUEST['db'] ) && $_REQUEST['db'] == 1 )
- $return['db_version'] = YOURLS_DB_VERSION;
- return yourls_apply_filter( 'api_result_version', $return );
+ $return['version'] = $return['simple'] = YOURLS_VERSION;
+ if( isset( $_REQUEST['db'] ) && $_REQUEST['db'] == 1 )
+ $return['db_version'] = YOURLS_DB_VERSION;
+ return yourls_apply_filter( 'api_result_version', $return );
}
/**
@@ -99,12 +99,12 @@ function yourls_api_action_version() {
* @return string API output, as an XML / JSON / JSONP / raw text string
*/
function yourls_api_output( $mode, $output, $send_headers = true, $echo = true ) {
- if( isset( $output['simple'] ) ) {
- $simple = $output['simple'];
- unset( $output['simple'] );
- }
+ if( isset( $output['simple'] ) ) {
+ $simple = $output['simple'];
+ unset( $output['simple'] );
+ }
- yourls_do_action( 'pre_api_output', $mode, $output, $send_headers, $echo );
+ yourls_do_action( 'pre_api_output', $mode, $output, $send_headers, $echo );
if( $send_headers ) {
if( isset( $output['statusCode'] ) ) {
@@ -119,43 +119,49 @@ function yourls_api_output( $mode, $output, $send_headers = true, $echo = true )
$result = '';
- switch ( $mode ) {
- case 'jsonp':
+ switch ( $mode ) {
+ case 'jsonp':
if( $send_headers )
yourls_content_type_header( 'application/javascript' );
- $callback = isset( $output['callback'] ) ? $output['callback'] : '';
- $result = $callback . '(' . json_encode( $output ) . ')';
- break;
+ $callback = isset( $output['callback'] ) ? yourls_validate_jsonp_callback($output['callback'] ) : '';
+ if( $callback === false ) {
+ yourls_status_header( 400 );
+ $result = json_encode( ['errorCode' => '400', 'error' => 'Invalid callback parameter'] );
+ } else {
+ $result = $callback . '(' . json_encode( $output ) . ')';
+ }
+
+ break;
- case 'json':
+ case 'json':
if( $send_headers )
yourls_content_type_header( 'application/json' );
- $result = json_encode( $output );
- break;
+ $result = json_encode( $output );
+ break;
- case 'xml':
+ case 'xml':
if( $send_headers )
yourls_content_type_header( 'application/xml' );
- $result = yourls_xml_encode( $output );
- break;
+ $result = yourls_xml_encode( $output );
+ break;
- case 'simple':
- default:
+ case 'simple':
+ default:
if( $send_headers )
yourls_content_type_header( 'text/plain' );
- $result = isset( $simple ) ? $simple : '';
- break;
- }
+ $result = isset( $simple ) ? $simple : '';
+ break;
+ }
if( $echo ) {
echo $result;
}
- yourls_do_action( 'api_output', $mode, $output, $send_headers, $echo );
+ yourls_do_action( 'api_output', $mode, $output, $send_headers, $echo );
return $result;
}
@@ -163,70 +169,79 @@ function yourls_api_output( $mode, $output, $send_headers = true, $echo = true )
/**
* Return array for API stat requests
*
+ * @param string $filter either "top", "bottom" , "rand" or "last"
+ * @param int $limit maximum number of links to return
+ * @param int $start offset
+ * @return array
*/
-function yourls_api_stats( $filter = 'top', $limit = 10, $start = 0 ) {
- $return = yourls_get_stats( $filter, $limit, $start );
- $return['simple'] = 'Need either XML or JSON format for stats';
- $return['message'] = 'success';
- return yourls_apply_filter( 'api_stats', $return, $filter, $limit, $start );
+function yourls_api_stats($filter = 'top', $limit = 10, $start = 0 ) {
+ $return = yourls_get_stats( $filter, $limit, $start );
+ $return['simple'] = 'Need either XML or JSON format for stats';
+ $return['message'] = 'success';
+ return yourls_apply_filter( 'api_stats', $return, $filter, $limit, $start );
}
/**
* Return array for counts of shorturls and clicks
*
+ * @return array
*/
function yourls_api_db_stats() {
- $return = array(
- 'db-stats' => yourls_get_db_stats(),
- 'statusCode' => 200,
- 'simple' => 'Need either XML or JSON format for stats',
- 'message' => 'success',
- );
-
- return yourls_apply_filter( 'api_db_stats', $return );
+ $return = array(
+ 'db-stats' => yourls_get_db_stats(),
+ 'statusCode' => '200',
+ 'simple' => 'Need either XML or JSON format for stats',
+ 'message' => 'success',
+ );
+
+ return yourls_apply_filter( 'api_db_stats', $return );
}
/**
* Return array for API stat requests
*
+ * @param string $shorturl Short URL to check
+ * @return array
*/
function yourls_api_url_stats( $shorturl ) {
- $keyword = str_replace( yourls_get_yourls_site() . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
- $keyword = yourls_sanitize_keyword( $keyword );
+ $keyword = str_replace( yourls_get_yourls_site() . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
+ $keyword = yourls_sanitize_keyword( $keyword );
- $return = yourls_get_keyword_stats( $keyword );
- $return['simple'] = 'Need either XML or JSON format for stats';
- return yourls_apply_filter( 'api_url_stats', $return, $shorturl );
+ $return = yourls_get_keyword_stats( $keyword );
+ $return['simple'] = 'Need either XML or JSON format for stats';
+ return yourls_apply_filter( 'api_url_stats', $return, $shorturl );
}
/**
* Expand short url to long url
*
+ * @param string $shorturl Short URL to expand
+ * @return array
*/
function yourls_api_expand( $shorturl ) {
- $keyword = str_replace( yourls_get_yourls_site() . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
- $keyword = yourls_sanitize_keyword( $keyword );
+ $keyword = str_replace( yourls_get_yourls_site() . '/' , '', $shorturl ); // accept either 'http://ozh.in/abc' or 'abc'
+ $keyword = yourls_sanitize_keyword( $keyword );
- $longurl = yourls_get_keyword_longurl( $keyword );
+ $longurl = yourls_get_keyword_longurl( $keyword );
- if( $longurl ) {
- $return = array(
- 'keyword' => $keyword,
- 'shorturl' => yourls_link($keyword),
- 'longurl' => $longurl,
+ if( $longurl ) {
+ $return = array(
+ 'keyword' => $keyword,
+ 'shorturl' => yourls_link($keyword),
+ 'longurl' => $longurl,
'title' => yourls_get_keyword_title( $keyword ),
- 'simple' => $longurl,
- 'message' => 'success',
- 'statusCode' => 200,
- );
- } else {
- $return = array(
- 'keyword' => $keyword,
- 'simple' => 'not found',
- 'message' => 'Error: short URL not found',
- 'errorCode' => 404,
- );
- }
-
- return yourls_apply_filter( 'api_expand', $return, $shorturl );
+ 'simple' => $longurl,
+ 'message' => 'success',
+ 'statusCode' => '200',
+ );
+ } else {
+ $return = array(
+ 'keyword' => $keyword,
+ 'simple' => 'not found',
+ 'message' => 'Error: short URL not found',
+ 'errorCode' => '404',
+ );
+ }
+
+ return yourls_apply_filter( 'api_expand', $return, $shorturl );
}
diff --git a/includes/functions-auth.php b/includes/functions-auth.php
index 44630e842..5d9685ad2 100644
--- a/includes/functions-auth.php
+++ b/includes/functions-auth.php
@@ -7,283 +7,314 @@
/**
* Show login form if required
*
+ * @return void
*/
function yourls_maybe_require_auth() {
- if( yourls_is_private() ) {
- yourls_do_action( 'require_auth' );
- require_once( YOURLS_INC.'/auth.php' );
- } else {
- yourls_do_action( 'require_no_auth' );
- }
+ if( yourls_is_private() ) {
+ yourls_do_action( 'require_auth' );
+ require_once( YOURLS_INC.'/auth.php' );
+ } else {
+ yourls_do_action( 'require_no_auth' );
+ }
}
/**
* Check for valid user via login form or stored cookie. Returns true or an error message
*
+ * @return bool|string|mixed true if valid user, error message otherwise. Can also call yourls_die() or redirect to login page. Oh my.
*/
function yourls_is_valid_user() {
- // Allow plugins to short-circuit the whole function
- $pre = yourls_apply_filter( 'shunt_is_valid_user', null );
- if ( null !== $pre ) {
- return $pre;
- }
-
- // $unfiltered_valid : are credentials valid? Boolean value. It's "unfiltered" to allow plugins to eventually filter it.
- $unfiltered_valid = false;
-
- // Logout request
- if( isset( $_GET['action'] ) && $_GET['action'] == 'logout' ) {
- yourls_do_action( 'logout' );
- yourls_store_cookie( null );
- return yourls__( 'Logged out successfully' );
- }
-
- // Check cookies or login request. Login form has precedence.
-
- yourls_do_action( 'pre_login' );
-
- // Determine auth method and check credentials
- if
- // API only: Secure (no login or pwd) and time limited token
- // ?timestamp=12345678&signature=md5(totoblah12345678)
- ( yourls_is_API() &&
- isset( $_REQUEST['timestamp'] ) && !empty($_REQUEST['timestamp'] ) &&
- isset( $_REQUEST['signature'] ) && !empty($_REQUEST['signature'] )
- )
- {
- yourls_do_action( 'pre_login_signature_timestamp' );
- $unfiltered_valid = yourls_check_signature_timestamp();
- }
-
- elseif
- // API only: Secure (no login or pwd)
- // ?signature=md5(totoblah)
- ( yourls_is_API() &&
- !isset( $_REQUEST['timestamp'] ) &&
- isset( $_REQUEST['signature'] ) && !empty( $_REQUEST['signature'] )
- )
- {
- yourls_do_action( 'pre_login_signature' );
- $unfiltered_valid = yourls_check_signature();
- }
-
- elseif
- // API or normal: login with username & pwd
- ( isset( $_REQUEST['username'] ) && isset( $_REQUEST['password'] )
- && !empty( $_REQUEST['username'] ) && !empty( $_REQUEST['password'] ) )
- {
- yourls_do_action( 'pre_login_username_password' );
- $unfiltered_valid = yourls_check_username_password();
- }
-
- elseif
- // Normal only: cookies
- ( !yourls_is_API() &&
- isset( $_COOKIE[ yourls_cookie_name() ] ) )
- {
- yourls_do_action( 'pre_login_cookie' );
- $unfiltered_valid = yourls_check_auth_cookie();
- }
-
- // Regardless of validity, allow plugins to filter the boolean and have final word
- $valid = yourls_apply_filter( 'is_valid_user', $unfiltered_valid );
-
- // Login for the win!
- if ( $valid ) {
- yourls_do_action( 'login' );
-
- // (Re)store encrypted cookie if needed
- if ( !yourls_is_API() ) {
- yourls_store_cookie( YOURLS_USER );
-
- // Login form : redirect to requested URL to avoid re-submitting the login form on page reload
- if( isset( $_REQUEST['username'] ) && isset( $_REQUEST['password'] ) && isset( $_SERVER['REQUEST_URI'] ) ) {
- // The return makes sure we exit this function before waiting for redirection.
- // This fixes #3189 and honestly I'm not sure why.
- return yourls_redirect( yourls_sanitize_url_safe($_SERVER['REQUEST_URI']) );
- }
- }
-
- // Login successful
- return true;
- }
-
- // Login failed
- yourls_do_action( 'login_failed' );
-
- if ( isset( $_REQUEST['username'] ) || isset( $_REQUEST['password'] ) ) {
- return yourls__( 'Invalid username or password' );
- } else {
- return yourls__( 'Please log in' );
- }
+ // Allow plugins to short-circuit the whole function
+ $pre = yourls_apply_filter( 'shunt_is_valid_user', yourls_shunt_default() );
+ if ( yourls_shunt_default() !== $pre ) {
+ return $pre;
+ }
+
+ // $unfiltered_valid : are credentials valid? Boolean value. It's "unfiltered" to allow plugins to eventually filter it.
+ $unfiltered_valid = false;
+
+ // Logout request
+ if( isset( $_GET['action'] ) && $_GET['action'] == 'logout' && isset( $_REQUEST['nonce'] ) ) {
+ // The logout nonce is associated to fake user 'logout' since at this point we don't know the real user
+ yourls_verify_nonce('admin_logout', $_REQUEST['nonce'], 'logout');
+ yourls_do_action( 'logout' );
+ yourls_store_cookie( '' );
+ return yourls__( 'Logged out successfully' );
+ }
+
+ // Check cookies or login request. Login form has precedence.
+
+ yourls_do_action( 'pre_login' );
+
+ // Determine auth method and check credentials
+ if
+ // API only: Secure (no login or pwd) and time limited token
+ // ?timestamp=12345678&signature=some-long-sha256-hash
+ ( yourls_is_API() &&
+ isset( $_REQUEST['timestamp'] ) && !empty($_REQUEST['timestamp'] ) &&
+ isset( $_REQUEST['signature'] ) && !empty($_REQUEST['signature'] )
+ )
+ {
+ yourls_do_action( 'pre_login_signature_timestamp' );
+ $unfiltered_valid = yourls_check_signature_timestamp();
+ }
+
+ elseif
+ // API only: Secure (no login or pwd)
+ // ?signature=some-long-sha256-hash
+ ( yourls_is_API() &&
+ !isset( $_REQUEST['timestamp'] ) &&
+ isset( $_REQUEST['signature'] ) && !empty( $_REQUEST['signature'] )
+ )
+ {
+ yourls_do_action( 'pre_login_signature' );
+ $unfiltered_valid = yourls_check_signature();
+ }
+
+ elseif
+ // API or normal: login with username & pwd
+ ( isset( $_REQUEST['username'] ) && isset( $_REQUEST['password'] )
+ && !empty( $_REQUEST['username'] ) && !empty( $_REQUEST['password'] ) )
+ {
+ yourls_do_action( 'pre_login_username_password' );
+ $unfiltered_valid = yourls_check_username_password();
+ }
+
+ elseif
+ // Normal only: cookies
+ ( !yourls_is_API() &&
+ isset( $_COOKIE[ yourls_cookie_name() ] ) )
+ {
+ yourls_do_action( 'pre_login_cookie' );
+ $unfiltered_valid = yourls_check_auth_cookie();
+ }
+
+ // Regardless of validity, allow plugins to filter the boolean and have final word
+ $valid = yourls_apply_filter( 'is_valid_user', $unfiltered_valid );
+
+ // Login for the win!
+ if ( $valid ) {
+ yourls_do_action( 'login' );
+
+ // (Re)store encrypted cookie if needed
+ if ( !yourls_is_API() ) {
+ yourls_store_cookie( YOURLS_USER );
+
+ // Login form : redirect to requested URL to avoid re-submitting the login form on page reload
+ if( isset( $_REQUEST['username'] ) && isset( $_REQUEST['password'] ) && isset( $_SERVER['REQUEST_URI'] ) ) {
+ // The return makes sure we exit this function before waiting for redirection.
+ // See #3189 and note in yourls_redirect()
+ return yourls_redirect( yourls_sanitize_url_safe($_SERVER['REQUEST_URI']) );
+ }
+ }
+
+ // Login successful
+ return true;
+ }
+
+ // Login failed
+ yourls_do_action( 'login_failed' );
+
+ if ( isset( $_REQUEST['username'] ) || isset( $_REQUEST['password'] ) ) {
+ return yourls__( 'Invalid username or password' );
+ } else {
+ return yourls__( 'Please log in' );
+ }
}
/**
* Check auth against list of login=>pwd. Sets user if applicable, returns bool
*
+ * @return bool true if login/pwd pair is valid (and sets user if applicable), false otherwise
*/
function yourls_check_username_password() {
- global $yourls_user_passwords;
+ global $yourls_user_passwords;
- // If login form (not API), check for nonce
+ // If login form (not API), check for nonce
if(!yourls_is_API()) {
yourls_verify_nonce('admin_login');
}
- if( isset( $yourls_user_passwords[ $_REQUEST['username'] ] ) && yourls_check_password_hash( $_REQUEST['username'], $_REQUEST['password'] ) ) {
- yourls_set_user( $_REQUEST['username'] );
- return true;
- }
- return false;
+ if( isset( $yourls_user_passwords[ $_REQUEST['username'] ] ) && yourls_check_password_hash( $_REQUEST['username'], $_REQUEST['password'] ) ) {
+ yourls_set_user( $_REQUEST['username'] );
+ return true;
+ }
+ return false;
}
/**
* Check a submitted password sent in plain text against stored password which can be a salted hash
*
+ * @param string $user
+ * @param string $submitted_password
+ * @return bool
*/
-function yourls_check_password_hash( $user, $submitted_password ) {
- global $yourls_user_passwords;
+function yourls_check_password_hash($user, $submitted_password ) {
+ global $yourls_user_passwords;
- if( !isset( $yourls_user_passwords[ $user ] ) )
- return false;
+ if( !isset( $yourls_user_passwords[ $user ] ) )
+ return false;
- if ( yourls_has_phpass_password( $user ) ) {
- // Stored password is hashed with phpass
- list( , $hash ) = explode( ':', $yourls_user_passwords[ $user ] );
- $hash = str_replace( '!', '$', $hash );
- return ( yourls_phpass_check( $submitted_password, $hash ) );
- } else if( yourls_has_md5_password( $user ) ) {
- // Stored password is a salted md5 hash: "md5:<$r = rand(10000,99999)>: header and logo
*
+ * @return void
*/
function yourls_html_logo() {
- yourls_do_action( 'pre_html_logo' );
- ?>
-
v ' . YOURLS_VERSION );
- $footer .= $num_queries;
- echo yourls_apply_filter( 'html_footer_text', $footer );
- ?>
-
- YOURLS: Your Own URL Shortener
-
-
-
+ YOURLS: Your Own URL Shortener
+
+
+
'; - echo join( "\n", yourls_get_debug_log() ); - echo ''; - } ?> - - - - + + +
'; + echo join( "\n", yourls_get_debug_log() ); + echo ''; + } ?> + + +