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..aea15a584 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@v6
- name: Use PHP ${{ matrix.php }}
- uses: shivammathur/setup-php@2.16.0
+ uses: shivammathur/setup-php@2.36.0
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..c583fbd1a
--- /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@v6
+
+ - name: Generate token
+ uses: actions/create-github-app-token@v2
+ 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..a92f21419
--- /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@v6
+
+ - name: Generate token
+ uses: actions/create-github-app-token@v2
+ 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..a084e77bb
--- /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@v6
+
+ - 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@v6
+ 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@v6
+ with:
+ repository: YOURLS/YOURLS.pot
+
+ - uses: actions/download-artifact@v7
+ with:
+ name: YOURLS-pot
+
+ - name: Generate token
+ uses: actions/create-github-app-token@v2
+ 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..1b2f7d1d4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,87 @@ 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.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 +116,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 +157,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..1a57344bf 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 Y our O wn URL S hortener, 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.
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=
- $url = urldecode( $_GET['u'] );
- } else {
- // New style bookmarklet: ?up=&us=&ur=
- $url = urldecode( $_GET['up'] . $_GET['us'] . $_GET['ur'] );
- }
- $keyword = ( isset( $_GET['k'] ) ? ( $_GET['k'] ) : '' );
- $title = ( isset( $_GET['t'] ) ? ( $_GET['t'] ) : '' );
- $return = yourls_add_new_link( $url, $keyword, $title );
-
- // If fails because keyword already exist, retry with no keyword
- if ( isset( $return['status'] ) && $return['status'] == 'fail' && isset( $return['code'] ) && $return['code'] == 'error:keyword' ) {
- $msg = $return['message'];
- $return = yourls_add_new_link( $url, '' );
- $return['message'] .= ' ('.$msg.')';
- }
-
- // Stop here if bookmarklet with a JSON callback function
- if( isset( $_GET['jsonp'] ) && $_GET['jsonp'] == 'yourls' ) {
- $short = $return['shorturl'] ? $return['shorturl'] : '';
- $message = $return['message'];
- yourls_content_type_header( 'application/javascript' );
- echo yourls_apply_filter( 'bookmarklet_jsonp', "yourls_callback({'short_url':'$short','message':'$message'});" );
-
- die();
- }
-
- // Now use the URL that has been sanitized and returned by yourls_add_new_link()
- $url = $return['url']['url'];
- $where['sql'] .= ' AND `url` LIKE :url ';
+ $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=
+ $url = $_GET['u'];
+ } else {
+ // New style bookmarklet: ?up=&us=&ur=
+ $url = $_GET['up'] . $_GET['us'] . $_GET['ur'];
+ }
+ $keyword = ( isset( $_GET['k'] ) ? ( $_GET['k'] ) : '' );
+ $title = ( isset( $_GET['t'] ) ? ( $_GET['t'] ) : '' );
+ $return = yourls_add_new_link( $url, $keyword, $title );
+
+ // If fails because keyword already exist, retry with no keyword
+ if ( isset( $return['status'] ) && $return['status'] == 'fail' && isset( $return['code'] ) && $return['code'] == 'error:keyword' ) {
+ $msg = $return['message'];
+ $return = yourls_add_new_link( $url, '' );
+ $return['message'] .= ' ('.$msg.')';
+ }
+
+ // Stop here if bookmarklet with a JSON callback function
+ if( isset( $_GET['jsonp'] ) && $_GET['jsonp'] == 'yourls' ) {
+ $short = $return['shorturl'] ? $return['shorturl'] : '';
+ $message = $return['message'];
+ yourls_content_type_header( 'application/javascript' );
+ echo yourls_apply_filter( 'bookmarklet_jsonp', "yourls_callback({'short_url':'$short','message':'$message'});" );
+
+ die();
+ }
+
+ // Now use the URL that has been sanitized and returned by yourls_add_new_link()
+ $url = $return['url']['url'];
+ $where['sql'] .= ' AND `url` LIKE :url ';
$where['binds']['url'] = $url;
- $page = $total_pages = $perpage = 1;
- $offset = 0;
-
- $text = ( isset( $_GET['s'] ) ? stripslashes( $_GET['s'] ) : '' );
-
- // Sharing with social bookmarklets
- if( !empty($_GET['share']) ) {
- yourls_do_action( 'pre_share_redirect' );
- switch ( $_GET['share'] ) {
- case 'twitter':
- // share with Twitter
- $destination = sprintf( "https://twitter.com/intent/tweet?url=%s&text=%s", urlencode( $return['shorturl'] ), urlencode( $title ) );
- yourls_redirect( $destination, 303 );
-
- // Deal with the case when redirection failed:
- $return['status'] = 'error';
- $return['errorCode'] = 400;
- $return['message'] = yourls_s( 'Short URL created, but could not redirect to %s !', 'Twitter' );
- break;
-
- case 'facebook':
- // share with Facebook
- $destination = sprintf( "https://www.facebook.com/sharer/sharer.php?u=%s&t=%s", urlencode( $return['shorturl'] ), urlencode( $title ) );
- yourls_redirect( $destination, 303 );
-
- // Deal with the case when redirection failed:
- $return['status'] = 'error';
- $return['errorCode'] = 400;
- $return['message'] = yourls_s( 'Short URL created, but could not redirect to %s !', 'Facebook' );
- break;
-
- case 'tumblr':
- // share with Tumblr
- $destination = sprintf( "https://www.tumblr.com/share?v=3&u=%s&t=%s&s=%s", urlencode( $return['shorturl'] ), urlencode( $title ), urlencode( $text ) );
- yourls_redirect( $destination, 303 );
-
- // Deal with the case when redirection failed:
- $return['status'] = 'error';
- $return['errorCode'] = 400;
- $return['message'] = yourls_s( 'Short URL created, but could not redirect to %s !', 'Tumblr' );
- break;
-
- default:
- // Is there a custom registered social bookmark?
- yourls_do_action( 'share_redirect_' . $_GET['share'], $return );
-
- // Still here? That was an unknown 'share' method, then.
- $return['status'] = 'error';
- $return['errorCode'] = 400;
- $return['message'] = yourls__( 'Unknown "Share" bookmarklet' );
- break;
- }
- }
+ $page = $total_pages = $perpage = 1;
+ $offset = 0;
+
+ $text = ( isset( $_GET['s'] ) ? stripslashes( $_GET['s'] ) : '' );
+
+ // Sharing with social bookmarklets
+ if( !empty($_GET['share']) ) {
+ yourls_do_action( 'pre_share_redirect' );
+ switch ( $_GET['share'] ) {
+ case 'twitter':
+ // share with Twitter
+ $destination = sprintf( "https://twitter.com/intent/tweet?url=%s&text=%s", urlencode( $return['shorturl'] ), urlencode( $title ) );
+ yourls_redirect( $destination, 303 );
+
+ // Deal with the case when redirection failed:
+ $return['status'] = 'error';
+ $return['errorCode'] = '400';
+ $return['message'] = yourls_s( 'Short URL created, but could not redirect to %s !', 'Twitter' );
+ break;
+
+ case 'facebook':
+ // share with Facebook
+ $destination = sprintf( "https://www.facebook.com/sharer/sharer.php?u=%s&t=%s", urlencode( $return['shorturl'] ), urlencode( $title ) );
+ yourls_redirect( $destination, 303 );
+
+ // Deal with the case when redirection failed:
+ $return['status'] = 'error';
+ $return['errorCode'] = '400';
+ $return['message'] = yourls_s( 'Short URL created, but could not redirect to %s !', 'Facebook' );
+ break;
+
+ case 'tumblr':
+ // share with Tumblr
+ $destination = sprintf( "https://www.tumblr.com/share?v=3&u=%s&t=%s&s=%s", urlencode( $return['shorturl'] ), urlencode( $title ), urlencode( $text ) );
+ yourls_redirect( $destination, 303 );
+
+ // Deal with the case when redirection failed:
+ $return['status'] = 'error';
+ $return['errorCode'] = '400';
+ $return['message'] = yourls_s( 'Short URL created, but could not redirect to %s !', 'Tumblr' );
+ break;
+
+ default:
+ // Is there a custom registered social bookmark?
+ yourls_do_action( 'share_redirect_' . $_GET['share'], $return );
+
+ // Still here? That was an unknown 'share' method, then.
+ $return['status'] = 'error';
+ $return['errorCode'] = '400';
+ $return['message'] = yourls__( 'Unknown "Share" bookmarklet' );
+ break;
+ }
+ }
// This is not a bookmarklet
} else {
- $is_bookmark = false;
-
- // Checking $page, $offset, $perpage
- if( empty($page) || $page == 0 ) {
- $page = 1;
- }
- if( empty($offset) ) {
- $offset = 0;
- }
- if( empty($perpage) || $perpage == 0) {
- $perpage = 50;
- }
-
- // Determine $offset
- $offset = ( $page-1 ) * $perpage;
-
- // Determine Max Number Of Items To Display On Page
- if( ( $offset + $perpage ) > $total_items ) {
- $max_on_page = $total_items;
- } else {
- $max_on_page = ( $offset + $perpage );
- }
-
- // Determine Number Of Items To Display On Page
- if ( ( $offset + 1 ) > $total_items ) {
- $display_on_page = $total_items;
- } else {
- $display_on_page = ( $offset + 1 );
- }
-
- // Determing Total Amount Of Pages
- $total_pages = ceil( $total_items / $perpage );
+ $is_bookmark = false;
+
+ // Checking $page, $offset, $perpage
+ if( empty($page) || $page == 0 ) {
+ $page = 1;
+ }
+ if( empty($offset) ) {
+ $offset = 0;
+ }
+ if( empty($perpage) || $perpage == 0) {
+ $perpage = 50;
+ }
+
+ // Determine $offset
+ $offset = ( $page-1 ) * $perpage;
+
+ // Determine Max Number Of Items To Display On Page
+ if( ( $offset + $perpage ) > $total_items ) {
+ $max_on_page = $total_items;
+ } else {
+ $max_on_page = ( $offset + $perpage );
+ }
+
+ // Determine Number Of Items To Display On Page
+ if ( ( $offset + 1 ) > $total_items ) {
+ $display_on_page = $total_items;
+ } else {
+ $display_on_page = ( $offset + 1 );
+ }
+
+ // Determine Total Amount Of Pages
+ $total_pages = ceil( $total_items / $perpage );
}
@@ -241,12 +244,18 @@
yourls_do_action( 'admin_page_before_content' );
if ( !$is_bookmark ) { ?>
-
- %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 ) ); ?>
$(document).ready(function(){
- feedback( "' . $return['message'] . '", "'. $return['status'] .'");
- init_clipboard();
- });';
+ echo '';
}
yourls_do_action( 'admin_page_before_table' );
@@ -270,42 +279,42 @@
yourls_table_head();
if ( !$is_bookmark ) {
- $params = array(
- 'search' => $search,
- 'search_text' => $search_text,
- 'search_in' => $search_in,
- 'sort_by' => $sort_by,
- 'sort_order' => $sort_order,
- 'page' => $page,
- 'perpage' => $perpage,
- 'click_filter' => $click_filter,
- 'click_limit' => $click_limit,
- 'total_pages' => $total_pages,
- 'date_filter' => $date_filter,
- 'date_first' => $date_first,
- 'date_second' => $date_second,
- );
- yourls_html_tfooter( $params );
+ $params = array(
+ 'search' => $search,
+ 'search_text' => $search_text,
+ 'search_in' => $search_in,
+ 'sort_by' => $sort_by,
+ 'sort_order' => $sort_order,
+ 'page' => $page,
+ 'perpage' => $perpage,
+ 'click_filter' => $click_filter,
+ 'click_limit' => $click_limit,
+ 'total_pages' => $total_pages,
+ 'date_filter' => $date_filter,
+ 'date_first' => $date_first,
+ 'date_second' => $date_second,
+ );
+ yourls_html_tfooter( $params );
}
yourls_table_tbody_start();
// Main Query
$where = yourls_apply_filter( 'admin_list_where', $where );
-$url_results = yourls_get_db()->fetchObjects( "SELECT * FROM `$table_url` WHERE 1=1 ${where['sql']} ORDER BY `$sort_by` $sort_order LIMIT $offset, $perpage;", $where['binds'] );
+$url_results = yourls_get_db('read-admin_index')->fetchObjects( "SELECT * FROM `$table_url` WHERE 1=1 {$where['sql']} ORDER BY `$sort_by` $sort_order LIMIT $offset, $perpage;", $where['binds'] );
$found_rows = false;
if( $url_results ) {
- $found_rows = true;
- foreach( $url_results as $url_result ) {
- $keyword = yourls_sanitize_keyword($url_result->keyword);
- $timestamp = strtotime( $url_result->timestamp );
- $url = stripslashes( $url_result->url );
- $ip = $url_result->ip;
- $title = $url_result->title ? $url_result->title : '';
- $clicks = $url_result->clicks;
-
- echo yourls_table_add_row( $keyword, $url, $title, $ip, $clicks, $timestamp );
- }
+ $found_rows = true;
+ foreach( $url_results as $url_result ) {
+ $keyword = yourls_sanitize_keyword($url_result->keyword);
+ $timestamp = strtotime( $url_result->timestamp );
+ $url = stripslashes( $url_result->url );
+ $ip = $url_result->ip;
+ $title = $url_result->title ? $url_result->title : '';
+ $clicks = $url_result->clicks;
+
+ echo yourls_table_add_row( $keyword, $url, $title, $ip, $clicks, $timestamp );
+ }
}
$display = $found_rows ? 'display:none' : '';
@@ -317,8 +326,10 @@
yourls_do_action( 'admin_page_after_table' );
+yourls_delete_link_modal();
+
if ( $is_bookmark )
- yourls_share_box( $url, $return['shorturl'], $title, $text );
+ yourls_share_box( $url, $return['shorturl'], $title, $text );
?>
diff --git a/admin/install.php b/admin/install.php
index bb20ed4b3..3d4b99a1a 100644
--- a/admin/install.php
+++ b/admin/install.php
@@ -2,7 +2,6 @@
define( 'YOURLS_ADMIN', true );
define( 'YOURLS_INSTALLING', true );
require_once( dirname( __DIR__ ).'/includes/load-yourls.php' );
-require_once( YOURLS_INC.'/functions-install.php' );
$error = array();
$warning = array();
@@ -10,44 +9,44 @@
// Check pre-requisites
if ( !yourls_check_PDO() ) {
- $error[] = yourls__( 'PHP extension for PDO not found' );
- yourls_debug_log( 'PHP PDO extension not found' );
+ $error[] = yourls__( 'PHP extension for PDO not found' );
+ yourls_debug_log( 'PHP PDO extension not found' );
}
if ( !yourls_check_database_version() ) {
- $error[] = yourls_s( '%s version is too old. Ask your server admin for an upgrade.', 'MySQL' );
- yourls_debug_log( 'MySQL version: ' . yourls_get_database_version() );
+ $error[] = yourls_s( '%s version is too old. Ask your server admin for an upgrade.', 'MySQL' );
+ yourls_debug_log( 'MySQL version: ' . yourls_get_database_version() );
}
if ( !yourls_check_php_version() ) {
- $error[] = yourls_s( '%s version is too old. Ask your server admin for an upgrade.', 'PHP' );
- yourls_debug_log( 'PHP version: ' . PHP_VERSION );
+ $error[] = yourls_s( '%s version is too old. Ask your server admin for an upgrade.', 'PHP' );
+ yourls_debug_log( 'PHP version: ' . PHP_VERSION );
}
// Is YOURLS already installed ?
if ( yourls_is_installed() ) {
- $error[] = yourls__( 'YOURLS already installed.' );
- // check if .htaccess exists, recreate otherwise. No error checking.
- if( !file_exists( YOURLS_ABSPATH.'/.htaccess' ) ) {
- yourls_create_htaccess();
- }
+ $error[] = yourls__( 'YOURLS already installed.' );
+ // check if .htaccess exists, recreate otherwise. No error checking.
+ if( !file_exists( YOURLS_ABSPATH.'/.htaccess' ) ) {
+ yourls_create_htaccess();
+ }
}
// Start install if possible and needed
if ( isset($_REQUEST['install']) && count( $error ) == 0 ) {
- // Create/update .htaccess file
- if ( yourls_create_htaccess() ) {
- $success[] = yourls__( 'File .htaccess successfully created/updated.' );
- } else {
- $warning[] = yourls__( 'Could not write file .htaccess in YOURLS root directory. You will have to do it manually. See how .' );
- }
+ // Create/update .htaccess file
+ if ( yourls_create_htaccess() ) {
+ $success[] = yourls__( 'File .htaccess successfully created/updated.' );
+ } else {
+ $warning[] = yourls__( 'Could not write file .htaccess in YOURLS root directory. You will have to do it manually. See how .' );
+ }
- // Create SQL tables
- $install = yourls_create_sql_tables();
- if ( isset( $install['error'] ) )
- $error = array_merge( $error, $install['error'] );
- if ( isset( $install['success'] ) )
- $success = array_merge( $success, $install['success'] );
+ // Create SQL tables
+ $install = yourls_create_sql_tables();
+ if ( isset( $install['error'] ) )
+ $error = array_merge( $error, $install['error'] );
+ if ( isset( $install['success'] ) )
+ $success = array_merge( $success, $install['success'] );
}
@@ -55,30 +54,30 @@
yourls_html_head( 'install', yourls__( 'Install YOURLS' ) );
?>
-
+ // Display install button or link to admin area if applicable
+ if( !yourls_is_installed() && !isset($_REQUEST['install']) ) {
+ echo '
';
+ } else {
+ if( count($error) == 0 )
+ echo '
» ' . yourls__( 'YOURLS Administration Page') . '
';
+ }
+ ?>
+
diff --git a/admin/plugins.php b/admin/plugins.php
index 123f63080..8d02b96dd 100644
--- a/admin/plugins.php
+++ b/admin/plugins.php
@@ -5,54 +5,56 @@
// Handle plugin administration pages
if( isset( $_GET['page'] ) && !empty( $_GET['page'] ) ) {
- yourls_plugin_admin_page( $_GET['page'] );
+ yourls_plugin_admin_page( $_GET['page'] );
die();
}
// Handle activation/deactivation of plugins
if( isset( $_GET['action'] ) ) {
- // Check nonce
- yourls_verify_nonce( 'manage_plugins', $_REQUEST['nonce'] );
-
- // Check plugin file is valid
- if( isset( $_GET['plugin'] ) && yourls_validate_plugin_file( YOURLS_PLUGINDIR.'/'.$_GET['plugin'].'/plugin.php') ) {
-
- // Activate / Deactive
- switch( $_GET['action'] ) {
- case 'activate':
- $result = yourls_activate_plugin( $_GET['plugin'].'/plugin.php' );
- if( $result === true )
- yourls_redirect( yourls_admin_url( 'plugins.php?success=activated' ), 302 );
-
- break;
-
- case 'deactivate':
- $result = yourls_deactivate_plugin( $_GET['plugin'].'/plugin.php' );
- if( $result === true )
- yourls_redirect( yourls_admin_url( 'plugins.php?success=deactivated' ), 302 );
-
- break;
-
- default:
- $result = yourls__( 'Unsupported action' );
- break;
- }
- } else {
- $result = yourls__( 'No plugin specified, or not a valid plugin' );
- }
-
- yourls_add_notice( $result );
+ // Check nonce
+ yourls_verify_nonce( 'manage_plugins', $_REQUEST['nonce'] ?? '');
+
+ // Check plugin file is valid
+ if(isset( $_GET['plugin'] ) && yourls_is_a_plugin_file(YOURLS_PLUGINDIR . '/' . $_GET['plugin'] . '/plugin.php') ) {
+
+ // Activate / Deactive
+ switch( $_GET['action'] ) {
+ case 'activate':
+ $result = yourls_activate_plugin( $_GET['plugin'].'/plugin.php' );
+ if( $result === true ) {
+ yourls_redirect(yourls_admin_url('plugins.php?success=activated'), 302);
+ exit();
+ }
+ break;
+
+ case 'deactivate':
+ $result = yourls_deactivate_plugin( $_GET['plugin'].'/plugin.php' );
+ if( $result === true ) {
+ yourls_redirect(yourls_admin_url('plugins.php?success=deactivated'), 302);
+ exit();
+ }
+ break;
+
+ default:
+ $result = yourls__( 'Unsupported action' );
+ break;
+ }
+ } else {
+ $result = yourls__( 'No plugin specified, or not a valid plugin' );
+ }
+
+ yourls_add_notice( $result );
}
-// Handle message upon succesfull (de)activation
+// Handle message upon successful (de)activation
if( isset( $_GET['success'] ) && ( ( $_GET['success'] == 'activated' ) OR ( $_GET['success'] == 'deactivated' ) ) ) {
- if( $_GET['success'] == 'activated' ) {
- $message = yourls__( 'Plugin has been activated' );
- } elseif ( $_GET['success'] == 'deactivated' ) {
- $message = yourls__( 'Plugin has been deactivated' );
- }
- yourls_add_notice( $message );
+ if( $_GET['success'] == 'activated' ) {
+ $message = yourls__( 'Plugin has been activated' );
+ } elseif ( $_GET['success'] == 'deactivated' ) {
+ $message = yourls__( 'Plugin has been deactivated' );
+ }
+ yourls_add_notice( $message );
}
yourls_html_head( 'plugins', yourls__( 'Manage Plugins' ) );
@@ -60,106 +62,110 @@
yourls_html_menu();
?>
-
-
-
-
-
- %1$s installed, and %2$s activated', $plugins_count, $count_active ); ?>
-
-
-
-
-
-
-
-
-
-
-
-
- $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( "%s %s %s %s %s ",
- $class, $data['uri'], $data['name'], $data['version'], $data['desc'], $data['author_uri'], $data['author'], $action_url, $action_anchor
- );
-
- }
- ?>
-
-
-
-
-
- plugin.php.' ); ?>
-
-
-
- Plugin list.' ); ?>
-
+
+
+
+
+
+ %1$s installed, and %2$s activated', $plugins_count, $count_active ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ $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( "%s %s %s %s %s ",
+ $class, $data['uri'], $data['name'], $data['version'], $data['desc'], $data['author_uri'], $data['author'], $action_url, $action_anchor
+ );
+
+ }
+ ?>
+
+
+
+
+
+ plugin.php.' ); ?>
+
+
+
+ Plugin list.' ); ?>
+
diff --git a/admin/tools.php b/admin/tools.php
index accb8c19f..b77206169 100644
--- a/admin/tools.php
+++ b/admin/tools.php
@@ -8,49 +8,49 @@
yourls_html_menu();
?>
-
+
-
+
- bookmarklets for easier link shortening and sharing.' ); ?>
+ bookmarklets for easier link shortening and sharing.' ); ?>
-
+
-
- Standard Bookmarklets will take you to a page where you can easily edit or delete your brand new short URL.' ); ?>
+
+ Standard Bookmarklets will take you to a page where you can easily edit or delete your brand new short URL.' ); ?>
- Instant Bookmarklets will pop the short URL without leaving the page you are viewing (depending on the page and server configuration, they may silently fail)' ); ?>
+ Instant Bookmarklets will pop the short URL without leaving the page you are viewing (depending on the page and server configuration, they may silently fail)' ); ?>
- Simple Bookmarklets will generate a short URL with a random or sequential keyword.' ); ?>
+ Simple Bookmarklets will generate a short URL with a random or sequential keyword.' ); ?>
- Custom Keyword Bookmarklets will prompt you for a custom keyword first.' ); ?>
-
+ Custom Keyword Bookmarklets will prompt you for a custom keyword first.' ); ?>
+
- 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.'); ?>
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
+
-
-
-
+
+
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
- %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 =
@@ -322,19 +322,19 @@
// $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..b77e8abb6 100644
--- a/composer.json
+++ b/composer.json
@@ -16,21 +16,31 @@
"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"
+ }
},
"autoload": {
"psr-4": {
@@ -38,8 +48,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..27941b6f7 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": "50804e4ad6386df5b147be4320a067e3",
"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.5",
"source": {
"type": "git",
"url": "https://github.com/composer/ca-bundle.git",
- "reference": "78a0e288fdcebf92aa2318a8d3656168da6ac1a5"
+ "reference": "08c50d5ec4c6ced7d0271d2862dec8c1033283e6"
},
"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/08c50d5ec4c6ced7d0271d2862dec8c1033283e6",
+ "reference": "08c50d5ec4c6ced7d0271d2862dec8c1033283e6",
"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.5"
},
"funding": [
{
@@ -137,31 +138,32 @@
"type": "tidelift"
}
],
- "time": "2021-01-12T12:10:35+00:00"
+ "time": "2025-01-08T16:17:16+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 +194,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,28 +237,27 @@
},
{
"name": "maxmind-db/reader",
- "version": "v1.9.0",
+ "version": "v1.12.0",
"source": {
"type": "git",
"url": "https://github.com/maxmind/MaxMind-DB-Reader-php.git",
- "reference": "9ee9ba9ee287b119e9f5a8e8dbfea0b49647cec4"
+ "reference": "5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90"
},
"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/5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90",
+ "reference": "5b2d7a721dedfaef9dc20822c5fe7d26f9f8eb90",
"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.*"
},
@@ -293,32 +294,33 @@
],
"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.12.0"
},
- "time": "2021-01-07T21:15:29+00:00"
+ "time": "2024-11-14T22:43:47+00:00"
},
{
"name": "maxmind/web-service-common",
- "version": "v0.8.1",
+ "version": "v0.10.0",
"source": {
"type": "git",
"url": "https://github.com/maxmind/web-service-common-php.git",
- "reference": "32f274051c543fc865e5a84d3a2c703913641ea8"
+ "reference": "d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4"
},
"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/d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4",
+ "reference": "d7c7c42fc31bff26e0ded73a6e187bcfb193f9c4",
"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.*",
+ "friendsofphp/php-cs-fixer": "3.*",
+ "phpstan/phpstan": "*",
"phpunit/phpunit": "^8.0 || ^9.0",
"squizlabs/php_codesniffer": "3.*"
},
@@ -343,26 +345,26 @@
"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.10.0"
},
- "time": "2020-11-02T17:00:53+00:00"
+ "time": "2024-11-14T23:14:52+00:00"
},
{
"name": "ozh/bookmarkletgen",
- "version": "1.2",
+ "version": "1.2.2",
"source": {
"type": "git",
"url": "https://github.com/ozh/bookmarkletgen.git",
- "reference": "3319b53c493a1474a03d1cc4e087617652284c20"
+ "reference": "65fffa64bb11f70470d398d7baf6d9bb84411cfc"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/ozh/bookmarkletgen/zipball/3319b53c493a1474a03d1cc4e087617652284c20",
- "reference": "3319b53c493a1474a03d1cc4e087617652284c20",
+ "url": "https://api.github.com/repos/ozh/bookmarkletgen/zipball/65fffa64bb11f70470d398d7baf6d9bb84411cfc",
+ "reference": "65fffa64bb11f70470d398d7baf6d9bb84411cfc",
"shasum": ""
},
"require": {
- "php": ">=5.3"
+ "php": ">=7.2"
},
"type": "library",
"autoload": {
@@ -388,81 +390,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/"
- }
+ "source": "https://github.com/ozh/bookmarkletgen/tree/1.2.2"
},
- "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"
- },
- "time": "2020-03-29T10:39:31+00:00"
+ "time": "2022-05-04T13:05:16+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 +437,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 +448,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 +497,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 +508,56 @@
"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.15",
"source": {
"type": "git",
"url": "https://github.com/WordPress/Requests.git",
- "reference": "afbe4790e4def03581c4a0963a1e8aa01f6030f1"
+ "reference": "877cd66169755899682f1595e057334b40d9d149"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/WordPress/Requests/zipball/afbe4790e4def03581c4a0963a1e8aa01f6030f1",
- "reference": "afbe4790e4def03581c4a0963a1e8aa01f6030f1",
+ "url": "https://api.github.com/repos/WordPress/Requests/zipball/877cd66169755899682f1595e057334b40d9d149",
+ "reference": "877cd66169755899682f1595e057334b40d9d149",
"shasum": ""
},
"require": {
- "php": ">=5.2"
+ "ext-json": "*",
+ "php": ">=5.6"
},
"require-dev": {
"dealerdirect/phpcodesniffer-composer-installer": "^0.7",
"php-parallel-lint/php-console-highlighter": "^0.5.0",
- "php-parallel-lint/php-parallel-lint": "^1.3",
+ "php-parallel-lint/php-parallel-lint": "^1.3.1",
"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"
+ "requests/test-server": "dev-main",
+ "roave/security-advisories": "dev-latest",
+ "squizlabs/php_codesniffer": "^3.6",
+ "wp-coding-standards/wpcs": "^2.0",
+ "yoast/phpunit-polyfills": "^1.0.0"
+ },
+ "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 +566,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 +593,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": "2025-01-21T10:13:31+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 +641,7 @@
{
"name": "Freek Van der Herten",
"email": "freek@spatie.be",
- "homepage": "https://murze.be",
+ "homepage": "https://freek.dev",
"role": "Developer"
}
],
@@ -660,50 +653,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.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-idn.git",
- "reference": "0eb8293dbbcd6ef6bf81404c9ce7d95bcdf34f44"
+ "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773"
},
"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/c36586dcf89a12315939e00ec9b4474adcb1d773",
+ "reference": "c36586dcf89a12315939e00ec9b4474adcb1d773",
"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 +732,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.31.0"
},
"funding": [
{
@@ -750,45 +748,42 @@
"type": "tidelift"
}
],
- "time": "2021-01-07T16:49:33+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-intl-normalizer",
- "version": "v1.22.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-intl-normalizer.git",
- "reference": "6e971c891537eb617a00bb07a43d182a6915faba"
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c"
},
"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/3833d7255cc303546435cb650316bff708a1c75c",
+ "reference": "3833d7255cc303546435cb650316bff708a1c75c",
"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.31.0"
},
"funding": [
{
@@ -834,41 +829,45 @@
"type": "tidelift"
}
],
- "time": "2021-01-07T17:09:11+00:00"
+ "time": "2024-09-09T11:45:10+00:00"
},
{
"name": "symfony/polyfill-mbstring",
- "version": "v1.15.0",
+ "version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac"
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
},
"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/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
"shasum": ""
},
"require": {
- "php": ">=5.3.3"
+ "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,83 +893,7 @@
"shim"
],
"support": {
- "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.15.0"
- },
- "funding": [
- {
- "url": "https://symfony.com/sponsor",
- "type": "custom"
- },
- {
- "url": "https://github.com/fabpot",
- "type": "github"
- },
- {
- "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
- "type": "tidelift"
- }
- ],
- "time": "2020-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"
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
},
"funding": [
{
@@ -986,19 +909,29 @@
"type": "tidelift"
}
],
- "time": "2021-01-07T16:49:33+00:00"
+ "time": "2024-09-09T11:45:10+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"
},
- "platform-dev": [],
- "plugin-api-version": "2.0.0"
+ "plugin-api-version": "2.6.0"
}
diff --git a/css/style.css b/css/style.css
index d7f50d971..1f108417d 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;}
@@ -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,8 +282,6 @@ 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%;
@@ -290,7 +289,6 @@ a.bookmarklet:hover {
margin-bottom:5px;
}
-
.jquery-notify-bar {
width:100%;
position:fixed;
@@ -305,11 +303,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 +329,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..cef031b26 100644
--- a/includes/Config/Config.php
+++ b/includes/Config/Config.php
@@ -11,20 +11,20 @@
class Config {
/**
- * @param string
+ * @var string
*/
protected $root;
/**
- * @param mixed
+ * @var string
*/
protected $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) {
+ public function __construct($config = '') {
$this->set_root( $this->fix_win32_path( dirname( dirname( __DIR__ ) ) ) );
$this->set_config($config);
}
@@ -42,7 +42,7 @@ public function fix_win32_path($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) {
@@ -51,7 +51,7 @@ public function set_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) {
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..3c256dbda 100644
--- a/includes/Database/Logger.php
+++ b/includes/Database/Logger.php
@@ -47,9 +47,9 @@ 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') {
$this->messages[] = sprintf(
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..803f34571 100644
--- a/includes/Database/YDB.php
+++ b/includes/Database/YDB.php
@@ -1,12 +1,12 @@
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
*/
@@ -33,7 +33,7 @@ class YDB extends ExtendedPdo {
protected $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
*
@@ -53,13 +53,13 @@ class YDB extends ExtendedPdo {
protected $option = [];
/**
- * Plugin admin pages informations
+ * Plugin admin pages information
* @var array
*/
protected $plugin_pages = [];
/**
- * Plugin informations
+ * Plugin information
* @var array
*/
protected $plugins = [];
@@ -78,15 +78,15 @@ class YDB extends ExtendedPdo {
* @param array $options Driver-specific options
* @param array $attributes Attributes to set after a connection
*/
- 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 +140,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,16 +158,18 @@ 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 .= ' ' . get_class($exception) .': ' . $exception->getMessage();
-
yourls_die( yourls__( $message ), yourls__( 'Fatal error' ), 503 );
die();
+
}
/**
@@ -192,6 +195,7 @@ public function start_profiler() {
/**
* @param string $context
+ * @return void
*/
public function set_html_context($context) {
$this->context = $context;
@@ -209,6 +213,7 @@ public function get_html_context() {
/**
* @param string $name
* @param mixed $value
+ * @return void
*/
public function set_option($name, $value) {
$this->option[$name] = $value;
@@ -232,6 +237,7 @@ public function get_option($name) {
/**
* @param string $name
+ * @return void
*/
public function delete_option($name) {
unset($this->option[$name]);
@@ -243,6 +249,7 @@ public function delete_option($name) {
/**
* @param string $keyword
* @param mixed $infos
+ * @return void
*/
public function set_infos($keyword, $infos) {
$this->infos[$keyword] = $infos;
@@ -266,9 +273,23 @@ public function get_infos($keyword) {
/**
* @param string $keyword
+ * @return void
*/
public function delete_infos($keyword) {
- unset($this->infos[$keyword]);
+ if (isset($this->infos[$keyword])) {
+ unset($this->infos[$keyword]);
+ }
+ }
+
+ /**
+ * @param string $keyword
+ * @param mixed $infos
+ * @return void
+ */
+ public function update_infos_if_exists($keyword, $infos) {
+ if ($this->has_infos($keyword) && $this->infos[$keyword]) {
+ $this->infos[$keyword] = array_merge($this->infos[$keyword], $infos);
+ }
}
/**
@@ -287,6 +308,7 @@ public function get_plugins() {
/**
* @param array $plugins
+ * @return void
*/
public function set_plugins(array $plugins) {
$this->plugins = $plugins;
@@ -294,6 +316,7 @@ public function set_plugins(array $plugins) {
/**
* @param string $plugin plugin filename
+ * @return void
*/
public function add_plugin($plugin) {
$this->plugins[] = $plugin;
@@ -301,6 +324,7 @@ public function add_plugin($plugin) {
/**
* @param string $plugin plugin filename
+ * @return void
*/
public function remove_plugin($plugin) {
unset($this->plugins[$plugin]);
@@ -318,6 +342,7 @@ public function get_plugin_pages() {
/**
* @param array $pages
+ * @return void
*/
public function set_plugin_pages(array $pages) {
$this->plugin_pages = $pages;
@@ -327,6 +352,7 @@ public function set_plugin_pages(array $pages) {
* @param string $slug
* @param string $title
* @param callable $function
+ * @return void
*/
public function add_plugin_page( $slug, $title, $function ) {
$this->plugin_pages[ $slug ] = [
@@ -338,6 +364,7 @@ public function add_plugin_page( $slug, $title, $function ) {
/**
* @param string $slug
+ * @return void
*/
public function remove_plugin_page( $slug ) {
unset( $this->plugin_pages[ $slug ] );
@@ -391,77 +418,13 @@ public function is_installed() {
}
/**
- * Return standardized DB version
- *
- * The regex removes everything that's not a number at the start of the string, or remove anything that's not a number and what
- * follows after that.
- * 'omgmysql-5.5-ubuntu-4.20' => '5.5'
- * 'mysql5.5-ubuntu-4.20' => '5.5'
- * '5.5-ubuntu-4.20' => '5.5'
- * '5.5-beta2' => '5.5'
- * '5.5' => '5.5'
+ * Return MySQL version
*
* @since 1.7.3
* @return string
*/
public function mysql_version() {
- $version = $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
- return $version;
+ return $this->pdo->getAttribute(PDO::ATTR_SERVER_VERSION);
}
- /**
- * Deprecated properties since 1.7.3, unused in 3rd party plugins as far as I know
- *
- * $ydb->DB_driver
- * $ydb->captured_errors
- * $ydb->dbh
- * $ydb->result
- * $ydb->rows_affected
- * $ydb->show_errors
- */
-
- /**
- * Deprecated functions since 1.7.3
- */
-
- // @codeCoverageIgnoreStart
-
- public function escape($string) {
- yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
- // This will escape using PDO->quote(), but then remove the enclosing quotes
- return substr($this->quote($string), 1, -1);
- }
-
- public function get_col($query) {
- yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
- yourls_debug_log('LEGACY SQL: '.$query);
- return $this->fetchCol($query);
- }
-
- public function get_results($query) {
- yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
- yourls_debug_log('LEGACY SQL: '.$query);
- $stm = parent::query($query);
- return($stm->fetchAll(PDO::FETCH_OBJ));
- }
-
- public function get_row($query) {
- yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
- yourls_debug_log('LEGACY SQL: '.$query);
- $row = $this->fetchObjects($query);
- return isset($row[0]) ? $row[0] : null;
- }
-
- public function get_var($query) {
- yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
- yourls_debug_log('LEGACY SQL: '.$query);
- return $this->fetchValue($query);
- }
-
- public function query($query, ...$unused) {
- yourls_deprecated_function( '$ydb->'.__FUNCTION__, '1.7.3', 'PDO' );
- yourls_debug_log('LEGACY SQL: '.$query);
- return $this->fetchAffected($query);
- }
- // @codeCoverageIgnoreEnd
}
diff --git a/includes/Views/AdminParams.php b/includes/Views/AdminParams.php
index 4ceb0b854..892684da6 100644
--- a/includes/Views/AdminParams.php
+++ b/includes/Views/AdminParams.php
@@ -51,9 +51,12 @@ class AdminParams
*/
public function __construct()
{
- $this->possible_search_params = ['all', 'keyword', 'url', 'title', 'ip'];
- $this->possible_sort_params = ['keyword', 'url', 'title', 'ip', 'timestamp', 'clicks'];
- $this->params_translations = [
+ // Cast return values of yourls_apply_filter() to array in case a hook would incorrectly return something else
+ $this->possible_search_params = (array)yourls_apply_filter('admin_params_possible_search',
+ ['all', 'keyword', 'url', 'title', 'ip']);
+ $this->possible_sort_params = (array)yourls_apply_filter('admin_params_possible_sort',
+ ['keyword', 'url', 'title', 'ip', 'timestamp', 'clicks']);
+ $this->params_translations = (array)yourls_apply_filter('admin_params_possible_translations',[
'all' => yourls__('All fields'),
'keyword' => yourls__('Short URL'),
'url' => yourls__('URL'),
@@ -61,8 +64,9 @@ public function __construct()
'ip' => yourls__('IP Address'),
'timestamp' => yourls__('Date'),
'clicks' => yourls__('Clicks'),
- ];
- $this->possible_date_sorting = ['before', 'after', 'between'];
+ ]);
+ $this->possible_date_sorting = (array)yourls_apply_filter('admin_params_possible_date_sort',
+ ['before', 'after', 'between']);
}
/**
@@ -152,7 +156,7 @@ public function get_search_in(): string
*
* @since 1.8.2
*
- * @return mixed
+ * @return string
*/
public function get_sort_by(): string
{
@@ -218,8 +222,15 @@ public function get_click_filter()
public function get_click_limit()
{
// @hook Default link click threshold (unset)
- return (isset($_GET['click_limit']) && intval($_GET['click_limit'])) ?
- intval($_GET['click_limit']) : yourls_apply_filter('admin_view_click_limit', '');
+ if (
+ isset($_GET['click_limit']) // Exists in the query string
+ && ($_GET['click_limit'] !== '') // Not empty (&stuff=&click_limit=&otherstuff=)
+ && intval($_GET['click_limit']) >= 0 // A number >= 0
+ ) {
+ return intval($_GET['click_limit']);
+ } else {
+ return yourls_apply_filter('admin_view_click_limit', '');
+ }
}
diff --git a/includes/auth.php b/includes/auth.php
index d6b95a89d..be98a87da 100644
--- a/includes/auth.php
+++ b/includes/auth.php
@@ -6,23 +6,23 @@
if( $auth !== true ) {
- // API mode,
- if ( yourls_is_API() ) {
- $format = ( isset($_REQUEST['format']) ? $_REQUEST['format'] : 'xml' );
- $callback = ( isset($_REQUEST['callback']) ? $_REQUEST['callback'] : '' );
- yourls_api_output( $format, array(
- 'simple' => $auth,
- 'message' => $auth,
- 'errorCode' => 403,
- 'callback' => $callback,
- ) );
-
- // Regular mode
- } else {
- yourls_login_screen( $auth );
- }
-
- die();
+ // API mode,
+ if ( yourls_is_API() ) {
+ $format = ( isset($_REQUEST['format']) ? $_REQUEST['format'] : 'xml' );
+ $callback = ( isset($_REQUEST['callback']) ? $_REQUEST['callback'] : '' );
+ yourls_api_output( $format, array(
+ 'simple' => $auth,
+ 'message' => $auth,
+ 'errorCode' => '403',
+ 'callback' => $callback,
+ ) );
+
+ // Regular mode
+ } else {
+ yourls_login_screen( $auth );
+ }
+
+ die();
}
yourls_do_action( 'auth_successful' );
@@ -36,15 +36,15 @@
// Did we just fail at encrypting passwords ?
if ( isset( $_GET['dismiss'] ) && $_GET['dismiss'] == 'hasherror' ) {
- yourls_update_option( 'defer_hashing_error', time() + 86400 * 7 ); // now + 1 week
+ yourls_update_option( 'defer_hashing_error', time() + 86400 * 7 ); // now + 1 week
} else {
- // Encrypt passwords that are clear text
- if ( yourls_maybe_hash_passwords() ) {
+ // Encrypt passwords that are clear text
+ if ( yourls_maybe_hash_passwords() ) {
$hash = yourls_hash_passwords_now( YOURLS_CONFIGFILE );
if ( $hash === true ) {
- // Hashing succesful. Remove flag from DB if any.
+ // Hashing successful. Remove flag from DB if any.
if( yourls_get_option( 'defer_hashing_error' ) )
yourls_delete_option( 'defer_hashing_error' );
} else {
@@ -59,5 +59,5 @@
yourls_add_notice( $message );
}
}
- }
+ }
}
diff --git a/includes/class-mysql.php b/includes/class-mysql.php
index d2ca2958e..603c87c83 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: ''.
+ * @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,18 @@ 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 ) );
+ $msg = 'Connected to ' . $dsn;
+ if ($context !== '') {
+ $msg .= ', context: ' . $context;
+ }
+ yourls_debug_log( $msg );
yourls_debug_mode( YOURLS_DEBUG );
@@ -66,7 +72,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 +81,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 );
+ $pre = yourls_apply_filter( 'shunt_get_db', false, $context );
if ( false !== $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 +135,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..cadf538b4 100644
--- a/includes/functions-auth.php
+++ b/includes/functions-auth.php
@@ -7,262 +7,277 @@
/**
* 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', 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' && 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=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.
+ // 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)>:"
- list( , $salt, ) = explode( ':', $yourls_user_passwords[ $user ] );
- return( $yourls_user_passwords[ $user ] == 'md5:'.$salt.':'.md5( $salt . $submitted_password ) );
- } else {
- // Password stored in clear text
- return( $yourls_user_passwords[ $user ] === $submitted_password );
- }
+ if ( yourls_has_phpass_password( $user ) ) {
+ // Stored password is hashed
+ 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)>:"
+ list( , $salt, ) = explode( ':', $yourls_user_passwords[ $user ] );
+ return( $yourls_user_passwords[ $user ] == 'md5:'.$salt.':'.md5( $salt . $submitted_password ) );
+ } else {
+ // Password stored in clear text
+ return( $yourls_user_passwords[ $user ] === $submitted_password );
+ }
}
/**
- * Overwrite plaintext passwords in config file with phpassed versions.
+ * Overwrite plaintext passwords in config file with hashed versions.
*
* @since 1.7
* @param string $config_file Full path to file
- * @return true if overwrite was successful, an error message otherwise
+ * @return true|string if overwrite was successful, an error message otherwise
*/
function yourls_hash_passwords_now( $config_file ) {
- if( !is_readable( $config_file ) )
- return 'cannot read file'; // not sure that can actually happen...
-
- if( !is_writable( $config_file ) )
- return 'cannot write file';
-
- // Include file to read value of $yourls_user_passwords
- // Temporary suppress error reporting to avoid notices about redeclared constants
- $errlevel = error_reporting();
- error_reporting( 0 );
- require $config_file;
- error_reporting( $errlevel );
-
- $configdata = file_get_contents( $config_file );
- if( $configdata == false )
- return 'could not read file';
-
- $to_hash = 0; // keep track of number of passwords that need hashing
- foreach ( $yourls_user_passwords as $user => $password ) {
- if ( !yourls_has_phpass_password( $user ) && !yourls_has_md5_password( $user ) ) {
- $to_hash++;
- $hash = yourls_phpass_hash( $password );
- // PHP would interpret $ as a variable, so replace it in storage.
- $hash = str_replace( '$', '!', $hash );
- $quotes = "'" . '"';
- $pattern = "/[$quotes]${user}[$quotes]\s*=>\s*[$quotes]" . preg_quote( $password, '/' ) . "[$quotes]/";
- $replace = "'$user' => 'phpass:$hash' /* Password encrypted by YOURLS */ ";
- $count = 0;
- $configdata = preg_replace( $pattern, $replace, $configdata, -1, $count );
- // There should be exactly one replacement. Otherwise, fast fail.
- if ( $count != 1 ) {
- yourls_debug_log( "Problem with preg_replace for password hash of user $user" );
- return 'preg_replace problem';
- }
- }
- }
-
- if( $to_hash == 0 )
- return 0; // There was no password to encrypt
-
- $success = file_put_contents( $config_file, $configdata );
- if ( $success === FALSE ) {
- yourls_debug_log( 'Failed writing to ' . $config_file );
- return 'could not write file';
- }
+ if( !is_readable( $config_file ) ) {
+ yourls_debug_log( 'Cannot hash passwords: cannot read file ' . $config_file );
+ return 'cannot read file'; // not sure that can actually happen...
+ }
+
+ if( !is_writable( $config_file ) ) {
+ yourls_debug_log( 'Cannot hash passwords: cannot write file ' . $config_file );
+ return 'cannot write file';
+ }
+
+ $yourls_user_passwords = [];
+ // Include file to read value of $yourls_user_passwords
+ // Temporary suppress error reporting to avoid notices about redeclared constants
+ $errlevel = error_reporting();
+ error_reporting( 0 );
+ require $config_file;
+ error_reporting( $errlevel );
+
+ $configdata = file_get_contents( $config_file );
+
+ if( $configdata == false ) {
+ yourls_debug_log('Cannot hash passwords: file_get_contents() false with ' . $config_file);
+ return 'could not read file';
+ }
+
+ $to_hash = 0; // keep track of number of passwords that need hashing
+ foreach ( $yourls_user_passwords as $user => $password ) {
+ // avoid "deprecated" warning when password is null -- see test case in tests/data/auth/preg_replace_problem.php
+ $password ??= '';
+ if ( !yourls_has_phpass_password( $user ) && !yourls_has_md5_password( $user ) ) {
+ $to_hash++;
+ $hash = yourls_phpass_hash( $password );
+ // PHP would interpret $ as a variable, so replace it in storage.
+ $hash = str_replace( '$', '!', $hash );
+ $quotes = "'" . '"';
+ $pattern = "/[$quotes]" . preg_quote( $user, '/' ) . "[$quotes]\s*=>\s*[$quotes]" . preg_quote( $password, '/' ) . "[$quotes]/";
+ $replace = "'$user' => 'phpass:$hash' /* Password encrypted by YOURLS */ ";
+ $count = 0;
+ $configdata = preg_replace( $pattern, $replace, $configdata, -1, $count );
+ // There should be exactly one replacement. Otherwise, fast fail.
+ if ( $count != 1 ) {
+ yourls_debug_log( "Problem with preg_replace for password hash of user $user" );
+ return 'preg_replace problem';
+ }
+ }
+ }
+
+ if( $to_hash == 0 ) {
+ yourls_debug_log('Cannot hash passwords: no password found in ' . $config_file);
+ return 'no password found';
+ }
+
+ $success = file_put_contents( $config_file, $configdata );
+ if ( $success === FALSE ) {
+ yourls_debug_log( 'Failed writing to ' . $config_file );
+ return 'could not write file';
+ }
yourls_debug_log('Successfully encrypted passwords in ' . basename($config_file));
- return true;
+ return true;
}
/**
- * Hash a password using phpass
+ * Create a password hash
*
* @since 1.7
* @param string $password password to hash
* @return string hashed password
*/
function yourls_phpass_hash( $password ) {
- $hasher = yourls_phpass_instance();
- return $hasher->HashPassword( $password );
+ /**
+ * Filter for hashing algorithm. See https://www.php.net/manual/en/function.password-hash.php
+ * Hashing algos are available if PHP was compiled with it.
+ * PASSWORD_BCRYPT is always available.
+ */
+ $algo = yourls_apply_filter('hash_algo', PASSWORD_BCRYPT);
+
+ /**
+ * Filter for hashing options. See https://www.php.net/manual/en/function.password-hash.php
+ * A typical option for PASSWORD_BCRYPT would be ['cost' => ]
+ * We're leaving the options at default values, which means a cost of 10 for PASSWORD_BCRYPT.
+ *
+ * If willing to modify this, be warned about the computing time, as there is a 2^n factor.
+ * See https://gist.github.com/ozh/65a75392b7cb254131cc55afd28de99b for examples.
+ */
+ $options = yourls_apply_filter('hash_options', [] );
+
+ return password_hash($password, $algo, $options);
}
/**
- * Check a clear password against a phpass hash
+ * Verify that a password matches a hash
*
* @since 1.7
* @param string $password clear (eg submitted in a form) password
- * @param string $hash hash supposedly generated by phpass
- * @return bool true if the hash matches the password once hashed by phpass, false otherwise
+ * @param string $hash hash
+ * @return bool true if the hash matches the password, false otherwise
*/
function yourls_phpass_check( $password, $hash ) {
- $hasher = yourls_phpass_instance();
- return $hasher->CheckPassword( $password, $hash );
-}
-
-/**
- * Helper function: create new instance or return existing instance of phpass class
- *
- * @since 1.7
- * @param int $iteration iteration count - 8 is default in phpass
- * @param bool $portable flag to force portable (cross platform and system independant) hashes - false to use whatever the system can do best
- * @return object a PasswordHash instance
- */
-function yourls_phpass_instance( $iteration = 8, $portable = false ) {
- $iteration = yourls_apply_filter( 'phpass_new_instance_iteration', $iteration );
- $portable = yourls_apply_filter( 'phpass_new_instance_portable', $portable );
-
- static $instance = false;
- if( $instance == false ) {
- $instance = new \Ozh\Phpass\PasswordHash( $iteration, $portable );
- }
-
- return $instance;
+ return password_verify($password, $hash);
}
@@ -273,17 +288,17 @@ function yourls_phpass_instance( $iteration = 8, $portable = false ) {
* @return bool true if any passwords are cleartext
*/
function yourls_has_cleartext_passwords() {
- global $yourls_user_passwords;
- foreach ( $yourls_user_passwords as $user => $pwdata ) {
- if ( !yourls_has_md5_password( $user ) && !yourls_has_phpass_password( $user ) ) {
- return true;
- }
- }
- return false;
+ global $yourls_user_passwords;
+ foreach ( $yourls_user_passwords as $user => $pwdata ) {
+ if ( !yourls_has_md5_password( $user ) && !yourls_has_phpass_password( $user ) ) {
+ return true;
+ }
+ }
+ return false;
}
/**
- * Check if a user has a hashed password
+ * Check if a user has a md5 hashed password
*
* Check if a user password is 'md5:[38 chars]'.
* TODO: deprecate this when/if we have proper user management with password hashes stored in the DB
@@ -293,43 +308,45 @@ function yourls_has_cleartext_passwords() {
* @return bool true if password hashed, false otherwise
*/
function yourls_has_md5_password( $user ) {
- global $yourls_user_passwords;
- return( isset( $yourls_user_passwords[ $user ] )
- && substr( $yourls_user_passwords[ $user ], 0, 4 ) == 'md5:'
- && strlen( $yourls_user_passwords[ $user ] ) == 42 // http://www.google.com/search?q=the+answer+to+life+the+universe+and+everything
- );
+ global $yourls_user_passwords;
+ return( isset( $yourls_user_passwords[ $user ] )
+ && substr( $yourls_user_passwords[ $user ], 0, 4 ) == 'md5:'
+ && strlen( $yourls_user_passwords[ $user ] ) == 42 // http://www.google.com/search?q=the+answer+to+life+the+universe+and+everything
+ );
}
/**
- * Check if a user's password is hashed with PHPASS.
+ * Check if a user's password is hashed with password_hash
*
* Check if a user password is 'phpass:[lots of chars]'.
+ * (For historical reason we're using 'phpass' as an identifier.)
* TODO: deprecate this when/if we have proper user management with password hashes stored in the DB
*
* @since 1.7
* @param string $user user login
- * @return bool true if password hashed with PHPASS, otherwise false
+ * @return bool true if password hashed with password_hash, otherwise false
*/
function yourls_has_phpass_password( $user ) {
- global $yourls_user_passwords;
- return( isset( $yourls_user_passwords[ $user ] )
- && substr( $yourls_user_passwords[ $user ], 0, 7 ) == 'phpass:'
- );
+ global $yourls_user_passwords;
+ return( isset( $yourls_user_passwords[ $user ] )
+ && substr( $yourls_user_passwords[ $user ], 0, 7 ) == 'phpass:'
+ );
}
/**
* Check auth against encrypted COOKIE data. Sets user if applicable, returns bool
*
+ * @return bool true if authenticated, false otherwise
*/
function yourls_check_auth_cookie() {
- global $yourls_user_passwords;
- foreach( $yourls_user_passwords as $valid_user => $valid_password ) {
- if ( yourls_cookie_value( $valid_user ) === $_COOKIE[ yourls_cookie_name() ] ) {
- yourls_set_user( $valid_user );
- return true;
- }
- }
- return false;
+ global $yourls_user_passwords;
+ foreach( $yourls_user_passwords as $valid_user => $valid_password ) {
+ if ( yourls_cookie_value( $valid_user ) === $_COOKIE[ yourls_cookie_name() ] ) {
+ yourls_set_user( $valid_user );
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -363,21 +380,21 @@ function yourls_check_signature_timestamp() {
return false;
}
- // Check signature & timestamp against all possible users
- global $yourls_user_passwords;
- foreach( $yourls_user_passwords as $valid_user => $valid_password ) {
- if (
+ // Check signature & timestamp against all possible users
+ global $yourls_user_passwords;
+ foreach( $yourls_user_passwords as $valid_user => $valid_password ) {
+ if (
hash( $hash_function, $_REQUEST['timestamp'].yourls_auth_signature( $valid_user ) ) === $_REQUEST['signature']
or
hash( $hash_function, yourls_auth_signature( $valid_user ).$_REQUEST['timestamp'] ) === $_REQUEST['signature']
- ) {
- yourls_set_user( $valid_user );
- return true;
- }
- }
+ ) {
+ yourls_set_user( $valid_user );
+ return true;
+ }
+ }
// Signature doesn't match known user
- return false;
+ return false;
}
/**
@@ -390,70 +407,77 @@ function yourls_check_signature() {
if( !isset( $_REQUEST['signature'] ) OR empty( $_REQUEST['signature'] ) )
return false;
- // Check signature against all possible users
+ // Check signature against all possible users
global $yourls_user_passwords;
- foreach( $yourls_user_passwords as $valid_user => $valid_password ) {
- if ( yourls_auth_signature( $valid_user ) === $_REQUEST['signature'] ) {
- yourls_set_user( $valid_user );
- return true;
- }
- }
+ foreach( $yourls_user_passwords as $valid_user => $valid_password ) {
+ if ( yourls_auth_signature( $valid_user ) === $_REQUEST['signature'] ) {
+ yourls_set_user( $valid_user );
+ return true;
+ }
+ }
// Signature doesn't match known user
- return false;
+ return false;
}
/**
* Generate secret signature hash
*
+ * @param false|string $username Username to generate signature for, or false to use current user
+ * @return string Signature
*/
function yourls_auth_signature( $username = false ) {
- if( !$username && defined('YOURLS_USER') ) {
- $username = YOURLS_USER;
- }
- return ( $username ? substr( yourls_salt( $username ), 0, 10 ) : 'Cannot generate auth signature: no username' );
+ if( !$username && defined('YOURLS_USER') ) {
+ $username = YOURLS_USER;
+ }
+ return ( $username ? substr( yourls_salt( $username ), 0, 10 ) : 'Cannot generate auth signature: no username' );
}
/**
* Check if timestamp is not too old
*
+ * @param int $time Timestamp to check
+ * @return bool True if timestamp is valid
*/
function yourls_check_timestamp( $time ) {
- $now = time();
- // Allow timestamp to be a little in the future or the past -- see Issue 766
- return yourls_apply_filter( 'check_timestamp', abs( $now - (int)$time ) < yourls_get_nonce_life(), $time );
+ $now = time();
+ // Allow timestamp to be a little in the future or the past -- see Issue 766
+ return yourls_apply_filter( 'check_timestamp', abs( $now - (int)$time ) < yourls_get_nonce_life(), $time );
}
/**
* Store new cookie. No $user will delete the cookie.
*
- * @param mixed $user String, user login, or null to delete cookie
+ * @param string $user User login, or empty string to delete cookie
+ * @return void
*/
-function yourls_store_cookie( $user = null ) {
+function yourls_store_cookie( $user = '' ) {
// No user will delete the cookie with a cookie time from the past
- if( !$user ) {
- $time = time() - 3600;
- } else {
- $time = time() + yourls_get_cookie_life();
- }
+ if( !$user ) {
+ $time = time() - 3600;
+ } else {
+ $time = time() + yourls_get_cookie_life();
+ }
$path = yourls_apply_filter( 'setcookie_path', '/' );
- $domain = yourls_apply_filter( 'setcookie_domain', parse_url( yourls_get_yourls_site(), PHP_URL_HOST ) );
- $secure = yourls_apply_filter( 'setcookie_secure', yourls_is_ssl() );
- $httponly = yourls_apply_filter( 'setcookie_httponly', true );
+ $domain = yourls_apply_filter( 'setcookie_domain', parse_url( yourls_get_yourls_site(), PHP_URL_HOST ) );
+ $secure = yourls_apply_filter( 'setcookie_secure', yourls_is_ssl() );
+ $httponly = yourls_apply_filter( 'setcookie_httponly', true );
+
+ // Some browsers refuse to store localhost cookie
+ if ( $domain == 'localhost' )
+ $domain = '';
- // Some browsers refuse to store localhost cookie
- if ( $domain == 'localhost' )
- $domain = '';
+ yourls_do_action( 'pre_setcookie', $user, $time, $path, $domain, $secure, $httponly );
if ( !headers_sent( $filename, $linenum ) ) {
yourls_setcookie( yourls_cookie_name(), yourls_cookie_value( $user ), $time, $path, $domain, $secure, $httponly );
- } else {
- // For some reason cookies were not stored: action to be able to debug that
- yourls_do_action( 'setcookie_failed', $user );
+ } else {
+ // For some reason cookies were not stored: action to be able to debug that
+ yourls_do_action( 'setcookie_failed', $user );
yourls_debug_log( "Could not store cookie: headers already sent in $filename on line $linenum" );
- }
+ }
}
/**
@@ -461,7 +485,6 @@ function yourls_store_cookie( $user = null ) {
*
* @see https://github.com/GoogleChromeLabs/samesite-examples/blob/master/php.md
* @see https://stackoverflow.com/a/59654832/36850
- * @see https://3v4l.org/uKEtH for compat tests
* @see https://www.php.net/manual/en/function.setcookie.php
*
* @since 1.7.7
@@ -477,28 +500,25 @@ function yourls_store_cookie( $user = null ) {
function yourls_setcookie($name, $value, $expire, $path, $domain, $secure, $httponly) {
$samesite = yourls_apply_filter('setcookie_samesite', 'Lax' );
- if (PHP_VERSION_ID < 70300) {
- return(setcookie($name, $value, $expire, "$path; samesite=$samesite", $domain, $secure, $httponly));
- }
- else {
- return(setcookie($name, $value, array(
- 'expires' => $expire,
- 'path' => $path,
- 'domain' => $domain,
- 'samesite' => $samesite,
- 'secure' => $secure,
- 'httponly' => $httponly,
- )));
- }
+ return(setcookie($name, $value, array(
+ 'expires' => $expire,
+ 'path' => $path,
+ 'domain' => $domain,
+ 'samesite' => $samesite,
+ 'secure' => $secure,
+ 'httponly' => $httponly,
+ )));
}
/**
* Set user name
*
+ * @param string $user Username
+ * @return void
*/
function yourls_set_user( $user ) {
- if( !defined( 'YOURLS_USER' ) )
- define( 'YOURLS_USER', $user );
+ if( !defined( 'YOURLS_USER' ) )
+ define( 'YOURLS_USER', $user );
}
/**
@@ -512,7 +532,7 @@ function yourls_set_user( $user ) {
* @return integer cookie life span, in seconds
*/
function yourls_get_cookie_life() {
- return yourls_apply_filter( 'get_cookie_life', YOURLS_COOKIE_LIFE );
+ return yourls_apply_filter( 'get_cookie_life', YOURLS_COOKIE_LIFE );
}
/**
@@ -527,7 +547,7 @@ function yourls_get_cookie_life() {
* @return integer nonce life span, in seconds
*/
function yourls_get_nonce_life() {
- return yourls_apply_filter( 'get_nonce_life', YOURLS_NONCE_LIFE );
+ return yourls_apply_filter( 'get_nonce_life', YOURLS_NONCE_LIFE );
}
/**
@@ -552,7 +572,7 @@ function yourls_cookie_name() {
* @return string cookie value
*/
function yourls_cookie_value( $user ) {
- return yourls_apply_filter( 'set_cookie_value', yourls_salt( $user ), $user );
+ return yourls_apply_filter( 'set_cookie_value', yourls_salt( $user ?? '' ), $user );
}
/**
@@ -560,95 +580,135 @@ function yourls_cookie_value( $user ) {
*
* Actually, this returns a float: ceil rounds up a value but is of type float, see https://www.php.net/ceil
*
+ * @return float
*/
function yourls_tick() {
- return ceil( time() / yourls_get_nonce_life() );
+ return ceil( time() / yourls_get_nonce_life() );
}
/**
- * Return salted string
+ * Return hashed string
+ *
+ * This function is badly named, it's not a salt or a salted string : it's a cryptographic hash.
*
+ * @since 1.4.1
+ * @param string $string string to salt
+ * @return string hashed string
*/
function yourls_salt( $string ) {
- $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
- return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
+ $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
+ return yourls_apply_filter( 'yourls_salt', hash_hmac( yourls_hmac_algo(), $string, $salt), $string );
+}
+
+/**
+ * Return an available hash_hmac() algorithm
+ *
+ * @since 1.8.3
+ * @return string hash_hmac() algorithm
+ */
+function yourls_hmac_algo() {
+ $algo = yourls_apply_filter( 'hmac_algo', 'sha256' );
+ if( !in_array( $algo, hash_hmac_algos() ) ) {
+ $algo = 'sha256';
+ }
+ return $algo;
}
/**
* Create a time limited, action limited and user limited token
*
+ * @param string $action Action to create nonce for
+ * @param false|string $user Optional user string, false for current user
+ * @return string Nonce token
*/
-function yourls_create_nonce( $action, $user = false ) {
- if( false == $user )
- $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
- $tick = yourls_tick();
- $nonce = substr( yourls_salt($tick . $action . $user), 0, 10 );
- // Allow plugins to alter the nonce
- return yourls_apply_filter( 'create_nonce', $nonce, $action, $user );
+function yourls_create_nonce($action, $user = false ) {
+ if( false === $user ) {
+ $user = defined('YOURLS_USER') ? YOURLS_USER : '-1';
+ }
+ $tick = yourls_tick();
+ $nonce = substr( yourls_salt($tick . $action . $user), 0, 10 );
+ // Allow plugins to alter the nonce
+ return yourls_apply_filter( 'create_nonce', $nonce, $action, $user );
}
/**
- * Create a nonce field for inclusion into a form
+ * Echoes or returns a nonce field for inclusion into a form
*
+ * @param string $action Action to create nonce for
+ * @param string $name Optional name of nonce field -- defaults to 'nonce'
+ * @param false|string $user Optional user string, false if unspecified
+ * @param bool $echo True to echo, false to return nonce field
+ * @return string Nonce field
*/
-function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) {
- $field = ' ';
- if( $echo )
- echo $field."\n";
- return $field;
+function yourls_nonce_field($action, $name = 'nonce', $user = false, $echo = true ) {
+ $field = ' ';
+ if( $echo )
+ echo $field."\n";
+ return $field;
}
/**
* Add a nonce to a URL. If URL omitted, adds nonce to current URL
*
+ * @param string $action Action to create nonce for
+ * @param string $url Optional URL to add nonce to -- defaults to current URL
+ * @param string $name Optional name of nonce field -- defaults to 'nonce'
+ * @param false|string $user Optional user string, false if unspecified
+ * @return string URL with nonce added
*/
-function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) {
- $nonce = yourls_create_nonce( $action, $user );
- return yourls_add_query_arg( $name, $nonce, $url );
+function yourls_nonce_url($action, $url = false, $name = 'nonce', $user = false ) {
+ $nonce = yourls_create_nonce( $action, $user );
+ return yourls_add_query_arg( $name, $nonce, $url );
}
/**
* Check validity of a nonce (ie time span, user and action match).
*
- * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined)
- * if $nonce is false or unspecified, it will use $_REQUEST['nonce']
+ * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined).
+ * If $nonce is false or unspecified, it will use $_REQUEST['nonce']
*
+ * @param string $action
+ * @param false|string $nonce Optional, string: nonce value, or false to use $_REQUEST['nonce']
+ * @param false|string $user Optional, string user, false for current user
+ * @param string $return Optional, string: message to die with if nonce is invalid
+ * @return bool|void True if valid, dies otherwise
*/
-function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
- // get user
- if( false == $user )
- $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
+function yourls_verify_nonce($action, $nonce = false, $user = false, $return = '' ) {
+ // Get user
+ if( false === $user ) {
+ $user = defined('YOURLS_USER') ? YOURLS_USER : '-1';
+ }
- // get current nonce value
- if( false == $nonce && isset( $_REQUEST['nonce'] ) )
- $nonce = $_REQUEST['nonce'];
+ // Get nonce value from $_REQUEST if not specified
+ if( false === $nonce && isset( $_REQUEST['nonce'] ) ) {
+ $nonce = $_REQUEST['nonce'];
+ }
- // Allow plugins to short-circuit the rest of the function
- $valid = yourls_apply_filter( 'verify_nonce', false, $action, $nonce, $user, $return );
- if ($valid) {
- return true;
- }
+ // Allow plugins to short-circuit the rest of the function
+ if (yourls_apply_filter( 'verify_nonce', false, $action, $nonce, $user, $return ) === true) {
+ return true;
+ }
- // what nonce should be
- $valid = yourls_create_nonce( $action, $user );
+ // What nonce should be
+ $valid = yourls_create_nonce( $action, $user );
- if( $nonce == $valid ) {
- return true;
- } else {
- if( $return )
- die( $return );
- yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 );
- }
+ if( $nonce === $valid ) {
+ return true;
+ } else {
+ if( $return )
+ die( $return );
+ yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 );
+ }
}
/**
* Check if YOURLS_USER comes from environment variables
*
* @since 1.8.2
- * @return bool true if YOURLS_USER and YOURLS_PASSWORD are defined as environment variables
+ * @return bool true if YOURLS_USER and YOURLS_PASSWORD are defined as environment variables
*/
function yourls_is_user_from_env() {
- return yourls_apply_filter('is_user_from_env', getenv('YOURLS_USER') && getenv('YOURLS_PASSWORD'));
+ return yourls_apply_filter('is_user_from_env', getenv('YOURLS_USER') && getenv('YOURLS_PASSWORD'));
}
@@ -657,7 +717,7 @@ function yourls_is_user_from_env() {
*
* By default, passwords are hashed. They are not if
* - there is no password in clear text in the config file (ie everything is already hashed)
- * - the user defined constant YOURLS_NO_HASH_PASSWORD is true, see https://github.com/YOURLS/YOURLS/wiki/Username-Passwords#but-i-dont-want-to-encrypt-my-password-
+ * - the user defined constant YOURLS_NO_HASH_PASSWORD is true, see https://docs.yourls.org/guide/essentials/credentials.html#i-don-t-want-to-encrypt-my-password
* - YOURLS_USER and YOURLS_PASSWORD are provided by the environment, not the config file
*
* @since 1.8.2
diff --git a/includes/functions-compat.php b/includes/functions-compat.php
index 4bc1330e4..2dc17d342 100644
--- a/includes/functions-compat.php
+++ b/includes/functions-compat.php
@@ -11,9 +11,9 @@
*
*/
if( !function_exists( 'json_encode' ) ) {
- function json_encode( $array ) {
- return yourls_array_to_json( $array );
- }
+ function json_encode( $array ) {
+ return yourls_array_to_json( $array );
+ }
}
/**
@@ -26,60 +26,60 @@ function json_encode( $array ) {
*/
function yourls_array_to_json( $array ){
- if( !is_array( $array ) ){
- return false;
- }
+ if( !is_array( $array ) ){
+ return false;
+ }
- $associative = count( array_diff( array_keys($array), array_keys( array_keys( $array )) ));
- if( $associative ){
+ $associative = count( array_diff( array_keys($array), array_keys( array_keys( $array )) ));
+ if( $associative ){
- $construct = array();
- foreach( $array as $key => $value ){
+ $construct = array();
+ foreach( $array as $key => $value ){
- // We first copy each key/value pair into a staging array,
- // formatting each key and value properly as we go.
+ // We first copy each key/value pair into a staging array,
+ // formatting each key and value properly as we go.
- // Format the key:
- if( is_numeric( $key ) ){
- $key = "key_$key";
- }
- $key = '"'.addslashes( $key ).'"';
+ // Format the key:
+ if( is_numeric( $key ) ){
+ $key = "key_$key";
+ }
+ $key = '"'.addslashes( $key ).'"';
- // Format the value:
- if( is_array( $value )){
- $value = yourls_array_to_json( $value );
- } else if( !is_numeric( $value ) || is_string( $value ) ){
- $value = '"'.addslashes( $value ).'"';
- }
+ // Format the value:
+ if( is_array( $value )){
+ $value = yourls_array_to_json( $value );
+ } else if( !is_numeric( $value ) || is_string( $value ) ){
+ $value = '"'.addslashes( $value ).'"';
+ }
- // Add to staging array:
- $construct[] = "$key: $value";
- }
+ // Add to staging array:
+ $construct[] = "$key: $value";
+ }
- // Then we collapse the staging array into the JSON form:
- $result = "{ " . implode( ", ", $construct ) . " }";
+ // Then we collapse the staging array into the JSON form:
+ $result = "{ " . implode( ", ", $construct ) . " }";
- } else { // If the array is a vector (not associative):
+ } else { // If the array is a vector (not associative):
- $construct = array();
- foreach( $array as $value ){
+ $construct = array();
+ foreach( $array as $value ){
- // Format the value:
- if( is_array( $value )){
- $value = yourls_array_to_json( $value );
- } else if( !is_numeric( $value ) || is_string( $value ) ){
- $value = '"'.addslashes($value).'"';
- }
+ // Format the value:
+ if( is_array( $value )){
+ $value = yourls_array_to_json( $value );
+ } else if( !is_numeric( $value ) || is_string( $value ) ){
+ $value = '"'.addslashes($value).'"';
+ }
- // Add to staging array:
- $construct[] = $value;
- }
+ // Add to staging array:
+ $construct[] = $value;
+ }
- // Then we collapse the staging array into the JSON form:
- $result = "[ " . implode( ", ", $construct ) . " ]";
- }
+ // Then we collapse the staging array into the JSON form:
+ $result = "[ " . implode( ", ", $construct ) . " ]";
+ }
- return $result;
+ return $result;
}
@@ -88,23 +88,23 @@ function yourls_array_to_json( $array ){
*
*/
if ( !function_exists( 'bcdiv' ) ) {
- function bcdiv( $dividend, $divisor ) {
- $quotient = floor( $dividend/$divisor );
- return $quotient;
- }
- function bcmod( $dividend, $modulo ) {
- $remainder = $dividend%$modulo;
- return $remainder;
- }
- function bcmul( $left, $right ) {
- return $left * $right;
- }
- function bcadd( $left, $right ) {
- return $left + $right;
- }
- function bcpow( $base, $power ) {
- return pow( $base, $power );
- }
+ function bcdiv( $dividend, $divisor ) {
+ $quotient = floor( $dividend/$divisor );
+ return $quotient;
+ }
+ function bcmod( $dividend, $modulo ) {
+ $remainder = $dividend%$modulo;
+ return $remainder;
+ }
+ function bcmul( $left, $right ) {
+ return $left * $right;
+ }
+ function bcadd( $left, $right ) {
+ return $left + $right;
+ }
+ function bcpow( $base, $power ) {
+ return pow( $base, $power );
+ }
}
// @codeCoverageIgnoreEnd
diff --git a/includes/functions-debug.php b/includes/functions-debug.php
index 85b4f0bbd..cac4f2d8b 100644
--- a/includes/functions-debug.php
+++ b/includes/functions-debug.php
@@ -6,8 +6,8 @@
/**
* Add a message to the debug log
*
- * When in debug mode ( YOURLS_DEBUG == true ) the debug log is echoed in yourls_html_footer()
- * Log messages are appended to $ydb->debug_log array, which is instanciated within class ezSQLcore_YOURLS
+ * When in debug mode (YOURLS_DEBUG == true) the debug log is echoed in yourls_html_footer()
+ * Log messages are appended to $ydb->debug_log array, which is instantiated within class Database\YDB
*
* @since 1.7
* @param string $msg Message to add to the debug log
@@ -15,9 +15,7 @@
*/
function yourls_debug_log( $msg ) {
yourls_do_action( 'debug_log', $msg );
- // Get the DB object ($ydb), get its profiler (\Aura\Sql\Profiler\Profiler), its logger (\Aura\Sql\Profiler\MemoryLogger) and
- // pass it a unused argument (loglevel) and the message
- yourls_get_db()->getProfiler()->getLogger()->log( 'debug', $msg);
+ yourls_get_db('read-debug_log')->getProfiler()->getLogger()->log('debug', $msg);
return $msg;
}
@@ -28,15 +26,16 @@ function yourls_debug_log( $msg ) {
* @return array
*/
function yourls_get_debug_log() {
- return yourls_get_db()->getProfiler()->getLogger()->getMessages();
+ return yourls_get_db('read-get_debug_log')->getProfiler()->getLogger()->getMessages();
}
/**
* Get number of SQL queries performed
*
+ * @return int
*/
function yourls_get_num_queries() {
- return yourls_apply_filter( 'get_num_queries', yourls_get_db()->get_num_queries() );
+ return yourls_apply_filter( 'get_num_queries', yourls_get_db('read-get_num_queries')->get_num_queries() );
}
/**
@@ -44,10 +43,11 @@ function yourls_get_num_queries() {
*
* @since 1.7.3
* @param bool $bool Debug on or off
+ * @return void
*/
function yourls_debug_mode( $bool ) {
// log queries if true
- yourls_get_db()->getProfiler()->setActive( (bool)$bool );
+ yourls_get_db('read-debug_mode')->getProfiler()->setActive( (bool)$bool );
// report notices if true
$level = $bool ? -1 : ( E_ERROR | E_PARSE );
diff --git a/includes/functions-deprecated.php b/includes/functions-deprecated.php
index daa2308ad..662ffb210 100644
--- a/includes/functions-deprecated.php
+++ b/includes/functions-deprecated.php
@@ -5,10 +5,94 @@
*
* Note to devs: when deprecating a function, move it here. Then check all the places
* in core that might be using it, including core plugins.
+ *
+ * Usage : yourls_deprecated_function( 'function_name', 'version', 'replacement' );
+ * Output: "{function_name} is deprecated since version {version}! Use {replacement} instead."
+ *
+ * Usage : yourls_deprecated_function( 'function_name', 'version' );
+ * Output: "{function_name} is deprecated since version {version} with no alternative available."
+ *
+ * @see yourls_deprecated_function()
*/
// @codeCoverageIgnoreStart
+/**
+ * Plugin activation sandbox
+ *
+ * @since 1.8.3
+ * @deprecated 1.9.2
+ * @param string $pluginfile Plugin filename (full path)
+ * @return string|true string if error or true if success
+ */
+function yourls_activate_plugin_sandbox( $pluginfile ) {
+ yourls_deprecated_function( __FUNCTION__, '1.9.1', 'yourls_include_file_sandbox');
+ return yourls_include_file_sandbox($pluginfile);
+}
+
+/**
+ * Return current admin page, or null if not an admin page. Was not used anywhere.
+ *
+ * @return mixed string if admin page, null if not an admin page
+ * @since 1.6
+ * @deprecated 1.9.1
+ */
+function yourls_current_admin_page() {
+ yourls_deprecated_function( __FUNCTION__, '1.9.1' );
+ if( yourls_is_admin() ) {
+ $current = substr( yourls_get_request(), 6 );
+ if( $current === false )
+ $current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php
+
+ return $current;
+ }
+ return null;
+}
+
+/**
+ * PHP emulation of JS's encodeURI
+ *
+ * @link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI
+ * @deprecated 1.9.1
+ * @param string $url
+ * @return string
+ */
+function yourls_encodeURI($url) {
+ yourls_deprecated_function( __FUNCTION__, '1.9.1', '' );
+ // Decode URL all the way
+ $result = yourls_rawurldecode_while_encoded( $url );
+ // Encode once
+ $result = strtr( rawurlencode( $result ), array (
+ '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@',
+ '%26' => '&', '%3D' => '=', '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*',
+ '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#',
+ ) );
+ // @TODO:
+ // Known limit: this will most likely break IDN URLs such as http://www.académie-française.fr/
+ // To fully support IDN URLs, advocate use of a plugin.
+ return yourls_apply_filter( 'encodeURI', $result, $url );
+}
+
+/**
+ * Check if a file is a plugin file
+ *
+ * @deprecated 1.8.3
+ */
+function yourls_validate_plugin_file( $file ) {
+ yourls_deprecated_function( __FUNCTION__, '1.8.3', 'yourls_is_a_plugin_file' );
+ return yourls_is_a_plugin_file($file);
+}
+
+/**
+ * Return a unique(ish) hash for a string to be used as a valid HTML id
+ *
+ * @deprecated 1.8.3
+ */
+function yourls_string2htmlid( $string ) {
+ yourls_deprecated_function( __FUNCTION__, '1.8.3', 'yourls_unique_element_id' );
+ return yourls_apply_filter( 'string2htmlid', 'y'.abs( crc32( $string ) ) );
+}
+
/**
* Get search text from query string variables search_protocol, search_slashes and search
*
@@ -45,12 +129,12 @@ function yourls_get_search_text() {
*/
function yourls_current_time( $type, $gmt = 0 ) {
yourls_deprecated_function( __FUNCTION__, '1.7.10', 'yourls_get_timestamp' );
- switch ( $type ) {
- case 'mysql':
- return ( $gmt ) ? gmdate( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s', yourls_get_timestamp( time() ));
- case 'timestamp':
- return ( $gmt ) ? time() : yourls_get_timestamp( time() );
- }
+ switch ( $type ) {
+ case 'mysql':
+ return ( $gmt ) ? gmdate( 'Y-m-d H:i:s' ) : gmdate( 'Y-m-d H:i:s', yourls_get_timestamp( time() ));
+ case 'timestamp':
+ return ( $gmt ) ? time() : yourls_get_timestamp( time() );
+ }
}
/**
@@ -85,8 +169,8 @@ function yourls_sanitize_string( $string, $restrict_to_shorturl_charset = false
*
*/
function yourls_favicon( $echo = true ) {
- yourls_deprecated_function( __FUNCTION__, '1.7.10', 'yourls_get_yourls_favicon_url' );
- return yourls_get_yourls_favicon_url( $echo );
+ yourls_deprecated_function( __FUNCTION__, '1.7.10', 'yourls_get_yourls_favicon_url' );
+ return yourls_get_yourls_favicon_url( $echo );
}
/**
@@ -96,8 +180,8 @@ function yourls_favicon( $echo = true ) {
*
*/
function yourls_get_link_stats( $url ) {
- yourls_deprecated_function( __FUNCTION__, '1.7.10', 'yourls_get_keyword_stats' );
- return yourls_get_keyword_stats( $url );
+ yourls_deprecated_function( __FUNCTION__, '1.7.10', 'yourls_get_keyword_stats' );
+ return yourls_get_keyword_stats( $url );
}
/**
@@ -108,8 +192,8 @@ function yourls_get_link_stats( $url ) {
*
*/
function yourls_url_exists( $url ) {
- yourls_deprecated_function( __FUNCTION__, '1.7.10', 'yourls_long_url_exists' );
- return yourls_long_url_exists( $url );
+ yourls_deprecated_function( __FUNCTION__, '1.7.10', 'yourls_long_url_exists' );
+ return yourls_long_url_exists( $url );
}
/**
@@ -117,8 +201,8 @@ function yourls_url_exists( $url ) {
*
*/
function yourls_plural( $word, $count=1 ) {
- yourls_deprecated_function( __FUNCTION__, '1.6', 'yourls_n' );
- return $word . ($count > 1 ? 's' : '');
+ yourls_deprecated_function( __FUNCTION__, '1.6', 'yourls_n' );
+ return $word . ($count > 1 ? 's' : '');
}
/**
@@ -126,10 +210,10 @@ function yourls_plural( $word, $count=1 ) {
*
*/
function yourls_get_duplicate_keywords( $longurl ) {
- yourls_deprecated_function( __FUNCTION__, '1.7', 'yourls_get_longurl_keywords' );
- if( !yourls_allow_duplicate_longurls() )
- return NULL;
- return yourls_apply_filter( 'get_duplicate_keywords', yourls_get_longurl_keywords ( $longurl ), $longurl );
+ yourls_deprecated_function( __FUNCTION__, '1.7', 'yourls_get_longurl_keywords' );
+ if( !yourls_allow_duplicate_longurls() )
+ return NULL;
+ return yourls_apply_filter( 'get_duplicate_keywords', yourls_get_longurl_keywords ( $longurl ), $longurl );
}
/**
@@ -139,8 +223,8 @@ function yourls_get_duplicate_keywords( $longurl ) {
*
*/
function yourls_intval( $int ) {
- yourls_deprecated_function( __FUNCTION__, '1.7', 'yourls_sanitize_int' );
- return yourls_escape( $int );
+ yourls_deprecated_function( __FUNCTION__, '1.7', 'yourls_sanitize_int' );
+ return yourls_escape( $int );
}
/**
@@ -148,8 +232,8 @@ function yourls_intval( $int ) {
*
*/
function yourls_get_remote_content( $url, $maxlen = 4096, $timeout = 5 ) {
- yourls_deprecated_function( __FUNCTION__, '1.7', 'yourls_http_get_body' );
- return yourls_http_get_body( $url );
+ yourls_deprecated_function( __FUNCTION__, '1.7', 'yourls_http_get_body' );
+ return yourls_http_get_body( $url );
}
/**
@@ -166,8 +250,8 @@ function yourls_get_remote_content( $url, $maxlen = 4096, $timeout = 5 ) {
* @return mixed
*/
function yourls_apply_filters( $hook, $value = '' ) {
- yourls_deprecated_function( __FUNCTION__, '1.7.1', 'yourls_apply_filter' );
- return yourls_apply_filter( $hook, $value );
+ yourls_deprecated_function( __FUNCTION__, '1.7.1', 'yourls_apply_filter' );
+ return yourls_apply_filter( $hook, $value );
}
/**
@@ -175,23 +259,22 @@ function yourls_apply_filters( $hook, $value = '' ) {
*
*/
function yourls_has_interface() {
- yourls_deprecated_function( __FUNCTION__, '1.7.1' );
- if( yourls_is_API() or yourls_is_GO() )
- return false;
- return true;
+ yourls_deprecated_function( __FUNCTION__, '1.7.1' );
+ if( yourls_is_API() or yourls_is_GO() )
+ return false;
+ return true;
}
/**
* Check if a proxy is defined for HTTP requests
*
- * @uses YOURLS_PROXY
* @since 1.7
* @deprecated 1.7.1
* @return bool true if a proxy is defined, false otherwise
*/
function yourls_http_proxy_is_defined() {
- yourls_deprecated_function( __FUNCTION__, '1.7.1', 'yourls_http_get_proxy' );
- return yourls_apply_filter( 'http_proxy_is_defined', defined( 'YOURLS_PROXY' ) );
+ yourls_deprecated_function( __FUNCTION__, '1.7.1', 'yourls_http_get_proxy' );
+ return yourls_apply_filter( 'http_proxy_is_defined', defined( 'YOURLS_PROXY' ) );
}
/**
@@ -209,8 +292,8 @@ function yourls_http_proxy_is_defined() {
* @return string Translated context string without pipe
*/
function yourls_ex( $text, $context, $domain = 'default' ) {
- yourls_deprecated_function( __FUNCTION__, '1.7.1', 'yourls_xe' );
- echo yourls_xe( $text, $context, $domain );
+ yourls_deprecated_function( __FUNCTION__, '1.7.1', 'yourls_xe' );
+ echo yourls_xe( $text, $context, $domain );
}
/**
@@ -224,20 +307,20 @@ function yourls_ex( $text, $context, $domain = 'default' ) {
* @return string|array escaped data
*/
function yourls_escape( $data ) {
- yourls_deprecated_function( __FUNCTION__, '1.7.3', 'PDO' );
- if( is_array( $data ) ) {
- foreach( $data as $k => $v ) {
- if( is_array( $v ) ) {
- $data[ $k ] = yourls_escape( $v );
- } else {
- $data[ $k ] = yourls_escape_real( $v );
- }
- }
- } else {
- $data = yourls_escape_real( $data );
- }
+ yourls_deprecated_function( __FUNCTION__, '1.7.3', 'PDO' );
+ if( is_array( $data ) ) {
+ foreach( $data as $k => $v ) {
+ if( is_array( $v ) ) {
+ $data[ $k ] = yourls_escape( $v );
+ } else {
+ $data[ $k ] = yourls_escape_real( $v );
+ }
+ }
+ } else {
+ $data = yourls_escape_real( $data );
+ }
- return $data;
+ return $data;
}
/**
@@ -254,13 +337,13 @@ function yourls_escape( $data ) {
* @return string escaped string
*/
function yourls_escape_real( $string ) {
- yourls_deprecated_function( __FUNCTION__, '1.7.3', 'PDO' );
- global $ydb;
- if( isset( $ydb ) && ( $ydb instanceof \YOURLS\Database\YDB ) )
- return $ydb->escape( $string );
+ yourls_deprecated_function( __FUNCTION__, '1.7.3', 'PDO' );
+ global $ydb;
+ if( isset( $ydb ) && ( $ydb instanceof \YOURLS\Database\YDB ) )
+ return $ydb->escape( $string );
- // YOURLS DB classes have been bypassed by a custom DB engine or a custom cache layer
- return yourls_apply_filter( 'custom_escape_real', addslashes( $string ), $string );
+ // YOURLS DB classes have been bypassed by a custom DB engine or a custom cache layer
+ return yourls_apply_filter( 'custom_escape_real', addslashes( $string ), $string );
}
// @codeCoverageIgnoreEnd
diff --git a/includes/functions-formatting.php b/includes/functions-formatting.php
index cb826b74a..e0c7f495d 100644
--- a/includes/functions-formatting.php
+++ b/includes/functions-formatting.php
@@ -7,47 +7,61 @@
/**
* Convert an integer (1337) to a string (3jk).
*
- */
-function yourls_int2string( $num, $chars = null ) {
- if( $chars == null )
- $chars = yourls_get_shorturl_charset();
- $string = '';
- $len = strlen( $chars );
- while( $num >= $len ) {
- $mod = bcmod( $num, $len );
- $num = bcdiv( $num, $len );
- $string = $chars[ $mod ] . $string;
- }
- $string = $chars[ intval( $num ) ] . $string;
+ * @param int $num Number to convert
+ * @param string $chars Characters to use for conversion
+ * @return string Converted number
+ */
+function yourls_int2string($num, $chars = null) {
+ if( $chars == null )
+ $chars = yourls_get_shorturl_charset();
+ $string = '';
+ $len = strlen( $chars );
+ while( $num >= $len ) {
+ $mod = bcmod( (string)$num, (string)$len );
+ $num = bcdiv( (string)$num, (string)$len );
+ $string = $chars[ $mod ] . $string;
+ }
+ $string = $chars[ intval( $num ) ] . $string;
- return yourls_apply_filter( 'int2string', $string, $num, $chars );
+ return yourls_apply_filter( 'int2string', $string, $num, $chars );
}
/**
* Convert a string (3jk) to an integer (1337)
*
- */
-function yourls_string2int( $string, $chars = null ) {
- if( $chars == null )
- $chars = yourls_get_shorturl_charset();
- $integer = 0;
- $string = strrev( $string );
- $baselen = strlen( $chars );
- $inputlen = strlen( $string );
- for ($i = 0; $i < $inputlen; $i++) {
- $index = strpos( $chars, $string[$i] );
- $integer = bcadd( $integer, bcmul( $index, bcpow( $baselen, $i ) ) );
- }
+ * @param string $string String to convert
+ * @param string $chars Characters to use for conversion
+ * @return string Number (as a string)
+ */
+function yourls_string2int($string, $chars = null) {
+ if( $chars == null )
+ $chars = yourls_get_shorturl_charset();
+ $integer = 0;
+ $string = strrev( $string );
+ $baselen = strlen( $chars );
+ $inputlen = strlen( $string );
+ for ($i = 0; $i < $inputlen; $i++) {
+ $index = strpos( $chars, $string[$i] );
+ $integer = bcadd( (string)$integer, bcmul( (string)$index, bcpow( (string)$baselen, (string)$i ) ) );
+ }
- return yourls_apply_filter( 'string2int', $integer, $string, $chars );
+ return yourls_apply_filter( 'string2int', $integer, $string, $chars );
}
/**
- * Return a unique(ish) hash for a string to be used as a valid HTML id
+ * Return a unique string to be used as a valid HTML id
*
+ * @since 1.8.3
+ * @param string $prefix Optional prefix
+ * @param int $initial_val The initial counter value (defaults to one)
+ * @return string The unique string
*/
-function yourls_string2htmlid( $string ) {
- return yourls_apply_filter( 'string2htmlid', 'y'.abs( crc32( $string ) ) );
+function yourls_unique_element_id($prefix = 'yid', $initial_val = 1) {
+ static $id_counter = 1;
+ if ($initial_val > 1) {
+ $id_counter = (int) $initial_val;
+ }
+ return yourls_apply_filter( 'unique_element_id', $prefix . (string) $id_counter++ );
}
/**
@@ -72,7 +86,7 @@ function yourls_sanitize_keyword( $keyword, $restrict_to_shorturl_charset = fals
$valid = yourls_sanitize_url( $keyword );
}
- return yourls_apply_filter( 'sanitize_string', $valid, $keyword, $restrict_to_shorturl_charset );
+ return yourls_apply_filter( 'sanitize_string', $valid, $keyword, $restrict_to_shorturl_charset );
}
/**
@@ -85,15 +99,15 @@ function yourls_sanitize_keyword( $keyword, $restrict_to_shorturl_charset = fals
* @return string Safe title
*/
function yourls_sanitize_title( $unsafe_title, $fallback = '' ) {
- $title = $unsafe_title;
- $title = strip_tags( $title );
- $title = preg_replace( "/\s+/", ' ', trim( $title ) );
+ $title = $unsafe_title;
+ $title = strip_tags( $title );
+ $title = preg_replace( "/\s+/", ' ', trim( $title ) );
if ( '' === $title || false === $title ) {
$title = $fallback;
}
- return yourls_apply_filter( 'sanitize_title', $title, $unsafe_title, $fallback );
+ return yourls_apply_filter( 'sanitize_title', $title, $unsafe_title, $fallback );
}
/**
@@ -106,8 +120,8 @@ function yourls_sanitize_title( $unsafe_title, $fallback = '' ) {
* @return string Safe URL
*/
function yourls_sanitize_url( $unsafe_url, $protocols = array() ) {
- $url = yourls_esc_url( $unsafe_url, 'redirection', $protocols );
- return yourls_apply_filter( 'sanitize_url', $url, $unsafe_url );
+ $url = yourls_esc_url( $unsafe_url, 'redirection', $protocols );
+ return yourls_apply_filter( 'sanitize_url', $url, $unsafe_url );
}
/**
@@ -125,8 +139,8 @@ function yourls_sanitize_url( $unsafe_url, $protocols = array() ) {
* @return string Safe URL
*/
function yourls_sanitize_url_safe( $unsafe_url, $protocols = array() ) {
- $url = yourls_esc_url( $unsafe_url, 'safe', $protocols );
- return yourls_apply_filter( 'sanitize_url_safe', $url, $unsafe_url );
+ $url = yourls_esc_url( $unsafe_url, 'safe', $protocols );
+ return yourls_apply_filter( 'sanitize_url_safe', $url, $unsafe_url );
}
/**
@@ -134,69 +148,85 @@ function yourls_sanitize_url_safe( $unsafe_url, $protocols = array() ) {
*
* Stolen from WP's _deep_replace
*
- */
-function yourls_deep_replace( $search, $subject ){
- $found = true;
- while($found) {
- $found = false;
- foreach( (array) $search as $val ) {
- while( strpos( $subject, $val ) !== false ) {
- $found = true;
- $subject = str_replace( $val, '', $subject );
- }
- }
- }
+ * @param string|array $search Needle, or array of needles.
+ * @param string $subject Haystack.
+ * @return string The string with the replaced values.
+ */
+function yourls_deep_replace($search, $subject ){
+ $found = true;
+ while($found) {
+ $found = false;
+ foreach( (array) $search as $val ) {
+ while( strpos( $subject, $val ) !== false ) {
+ $found = true;
+ $subject = str_replace( $val, '', $subject );
+ }
+ }
+ }
- return $subject;
+ return $subject;
}
/**
* Make sure an integer is a valid integer (PHP's intval() limits to too small numbers)
*
+ * @param int $int Integer to check
+ * @return string Integer as a string
*/
-function yourls_sanitize_int( $int ) {
- return ( substr( preg_replace( '/[^0-9]/', '', strval( $int ) ), 0, 20 ) );
+function yourls_sanitize_int($int ) {
+ return ( substr( preg_replace( '/[^0-9]/', '', strval( $int ) ), 0, 20 ) );
}
/**
* Sanitize an IP address
+ * No check on validity, just return a sanitized string
*
+ * @param string $ip IP address
+ * @return string IP address
*/
-function yourls_sanitize_ip( $ip ) {
- return preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip );
+function yourls_sanitize_ip($ip ) {
+ return preg_replace( '/[^0-9a-fA-F:., ]/', '', $ip );
}
/**
* Make sure a date is m(m)/d(d)/yyyy, return false otherwise
*
+ * @param string $date Date to check
+ * @return false|mixed Date in format m(m)/d(d)/yyyy or false if invalid
*/
-function yourls_sanitize_date( $date ) {
- if( !preg_match( '!^\d{1,2}/\d{1,2}/\d{4}$!' , $date ) ) {
- return false;
- }
- return $date;
+function yourls_sanitize_date($date ) {
+ if( !preg_match( '!^\d{1,2}/\d{1,2}/\d{4}$!' , $date ) ) {
+ return false;
+ }
+ return $date;
}
/**
* Sanitize a date for SQL search. Return false if malformed input.
*
+ * @param string $date Date
+ * @return false|string String in Y-m-d format for SQL search or false if malformed input
*/
-function yourls_sanitize_date_for_sql( $date ) {
- if( !yourls_sanitize_date( $date ) )
- return false;
- return date( 'Y-m-d', strtotime( $date ) );
+function yourls_sanitize_date_for_sql($date) {
+ if( !yourls_sanitize_date( $date ) )
+ return false;
+ return date( 'Y-m-d', strtotime( $date ) );
}
/**
- * Return trimmed string
+ * Return trimmed string, optionally append '[...]' if string is too long
*
+ * @param string $string String to trim
+ * @param int $length Maximum length of string
+ * @param string $append String to append if trimmed
+ * @return string Trimmed string
*/
-function yourls_trim_long_string( $string, $length = 60, $append = '[...]' ) {
- $newstring = $string;
+function yourls_trim_long_string($string, $length = 60, $append = '[...]') {
+ $newstring = $string;
if ( mb_strlen( $newstring ) > $length ) {
$newstring = mb_substr( $newstring, 0, $length - mb_strlen( $append ), 'UTF-8' ) . $append;
}
- return yourls_apply_filter( 'trim_long_string', $newstring, $string, $length, $append );
+ return yourls_apply_filter( 'trim_long_string', $newstring, $string, $length, $append );
}
/**
@@ -204,7 +234,12 @@ function yourls_trim_long_string( $string, $length = 60, $append = '[...]' ) {
*
* The regexp searches for the first digits, then a period, then more digits and periods, and discards
* all the rest.
- * For instance, 'mysql-5.5-beta' and '5.5-RC1' return '5.5'
+ * Examples:
+ * 'omgmysql-5.5-ubuntu-4.20' => '5.5'
+ * 'mysql5.5-ubuntu-4.20' => '5.5'
+ * '5.5-ubuntu-4.20' => '5.5'
+ * '5.5-beta2' => '5.5'
+ * '5.5' => '5.5'
*
* @since 1.4.1
* @param string $version Version number
@@ -220,34 +255,71 @@ function yourls_sanitize_version( $version ) {
/**
* Sanitize a filename (no Win32 stuff)
*
+ * @param string $file File name
+ * @return string|null Sanitized file name (or null if it's just backslashes, ok...)
*/
-function yourls_sanitize_filename( $file ) {
- $file = str_replace( '\\', '/', $file ); // sanitize for Win32 installs
- $file = preg_replace( '|/+|' ,'/', $file ); // remove any duplicate slash
- return $file;
+function yourls_sanitize_filename($file) {
+ $file = str_replace( '\\', '/', $file ); // sanitize for Win32 installs
+ $file = preg_replace( '|/+|' ,'/', $file ); // remove any duplicate slash
+ return $file;
}
/**
- * Check if a string seems to be UTF-8. Stolen from WP.
+ * Validate a JSONP callback name
+ *
+ * Check if the callback contains only safe characters: [a-zA-Z0-9_$.]
+ * Returns the original callback if valid, or false if invalid.
*
+ * Examples:
+ * - 'myCallback' => 'myCallback'
+ * - 'alert(1)' => false
+ * See tests/tests/format/JsonpCallbackTest.php for various cases covered
+ *
+ * @since 1.10.3
+ * @param string $callback Raw callback value
+ * @return string|false Original callback if valid, false otherwise
*/
-function yourls_seems_utf8( $str ) {
- $length = strlen( $str );
- for ( $i=0; $i < $length; $i++ ) {
- $c = ord( $str[ $i ] );
- if ( $c < 0x80 ) $n = 0; # 0bbbbbbb
- elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
- elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
- elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
- elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb
- elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b
- else return false; # Does not match any model
- for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
- if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
- return false;
- }
- }
- return true;
+function yourls_validate_jsonp_callback($callback ) {
+ $callback = (string) $callback;
+
+ // First, check for JavaScript unicode escape sequences like \u2028 or u2028
+ // They are sometimes used to smuggle line/paragraph separators.
+ if ( preg_match( '/\\\\?u[0-9a-fA-F]{4}/', $callback ) ) {
+ return yourls_apply_filter( 'validate_jsonp_callback_error', false, $callback );
+ }
+
+ // Check if callback contains only safe characters [a-zA-Z0-9_$.]
+ if ( !preg_match( '/^[a-zA-Z0-9_$.]+$/', $callback ) ) {
+ return yourls_apply_filter( 'validate_jsonp_callback_error', false, $callback );
+ }
+
+ // Callback is valid, return original value
+ return yourls_apply_filter( 'validate_jsonp_callback', $callback );
+}
+
+/**
+ * Check if a string seems to be UTF-8. Stolen from WP.
+ *
+ * @param string $str String to check
+ * @return bool Whether string seems valid UTF-8
+ */
+function yourls_seems_utf8($str) {
+ $length = strlen( $str );
+ for ( $i=0; $i < $length; $i++ ) {
+ $c = ord( $str[ $i ] );
+ if ( $c < 0x80 ) $n = 0; # 0bbbbbbb
+ elseif (($c & 0xE0) == 0xC0) $n=1; # 110bbbbb
+ elseif (($c & 0xF0) == 0xE0) $n=2; # 1110bbbb
+ elseif (($c & 0xF8) == 0xF0) $n=3; # 11110bbb
+ elseif (($c & 0xFC) == 0xF8) $n=4; # 111110bb
+ elseif (($c & 0xFE) == 0xFC) $n=5; # 1111110b
+ else return false; # Does not match any model
+ for ($j=0; $j<$n; $j++) { # n bytes matching 10bbbbbb follow ?
+ if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80))
+ return false;
+ }
+ }
+ return true;
}
@@ -279,28 +351,28 @@ function yourls_supports_pcre_u() {
* @return string The checked text.
*/
function yourls_check_invalid_utf8( $string, $strip = false ) {
- $string = (string) $string;
+ $string = (string) $string;
- if ( 0 === strlen( $string ) ) {
- return '';
- }
+ if ( 0 === strlen( $string ) ) {
+ return '';
+ }
- // We can't demand utf8 in the PCRE installation, so just return the string in those cases
- if ( ! yourls_supports_pcre_u() ) {
- return $string;
- }
+ // We can't demand utf8 in the PCRE installation, so just return the string in those cases
+ if ( ! yourls_supports_pcre_u() ) {
+ return $string;
+ }
- // preg_match fails when it encounters invalid UTF8 in $string
- if ( 1 === @preg_match( '/^./us', $string ) ) {
- return $string;
- }
+ // preg_match fails when it encounters invalid UTF8 in $string
+ if ( 1 === @preg_match( '/^./us', $string ) ) {
+ return $string;
+ }
- // Attempt to strip the bad chars if requested (not recommended)
- if ( $strip && function_exists( 'iconv' ) ) {
- return iconv( 'utf-8', 'utf-8', $string );
- }
+ // Attempt to strip the bad chars if requested (not recommended)
+ if ( $strip && function_exists( 'iconv' ) ) {
+ return iconv( 'utf-8', 'utf-8', $string );
+ }
- return '';
+ return '';
}
/**
@@ -319,56 +391,56 @@ function yourls_check_invalid_utf8( $string, $strip = false ) {
* @return string The encoded text with HTML entities.
*/
function yourls_specialchars( $string, $quote_style = ENT_NOQUOTES, $double_encode = false ) {
- $string = (string) $string;
+ $string = (string) $string;
- if ( 0 === strlen( $string ) )
- return '';
+ if ( 0 === strlen( $string ) )
+ return '';
- // Don't bother if there are no specialchars - saves some processing
- if ( ! preg_match( '/[&<>"\']/', $string ) )
- return $string;
+ // Don't bother if there are no specialchars - saves some processing
+ if ( ! preg_match( '/[&<>"\']/', $string ) )
+ return $string;
- // Account for the previous behaviour of the function when the $quote_style is not an accepted value
- if ( empty( $quote_style ) )
- $quote_style = ENT_NOQUOTES;
- elseif ( ! in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) )
- $quote_style = ENT_QUOTES;
+ // Account for the previous behaviour of the function when the $quote_style is not an accepted value
+ if ( empty( $quote_style ) )
+ $quote_style = ENT_NOQUOTES;
+ elseif ( ! in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) )
+ $quote_style = ENT_QUOTES;
- $charset = 'UTF-8';
+ $charset = 'UTF-8';
- $_quote_style = $quote_style;
+ $_quote_style = $quote_style;
- if ( $quote_style === 'double' ) {
- $quote_style = ENT_COMPAT;
- $_quote_style = ENT_COMPAT;
- } elseif ( $quote_style === 'single' ) {
- $quote_style = ENT_NOQUOTES;
- }
+ if ( $quote_style === 'double' ) {
+ $quote_style = ENT_COMPAT;
+ $_quote_style = ENT_COMPAT;
+ } elseif ( $quote_style === 'single' ) {
+ $quote_style = ENT_NOQUOTES;
+ }
- // Handle double encoding ourselves
- if ( $double_encode ) {
- $string = @htmlspecialchars( $string, $quote_style, $charset );
- } else {
- // Decode & into &
- $string = yourls_specialchars_decode( $string, $_quote_style );
+ // Handle double encoding ourselves
+ if ( $double_encode ) {
+ $string = @htmlspecialchars( $string, $quote_style, $charset );
+ } else {
+ // Decode & into &
+ $string = yourls_specialchars_decode( $string, $_quote_style );
- // Guarantee every &entity; is valid or re-encode the &
- $string = yourls_kses_normalize_entities( $string );
+ // Guarantee every &entity; is valid or re-encode the &
+ $string = yourls_kses_normalize_entities( $string );
- // Now re-encode everything except &entity;
- $string = preg_split( '/(?x?[0-9a-z]+;)/i', $string, -1, PREG_SPLIT_DELIM_CAPTURE );
+ // Now re-encode everything except &entity;
+ $string = preg_split( '/(?x?[0-9a-z]+;)/i', $string, -1, PREG_SPLIT_DELIM_CAPTURE );
- for ( $i = 0; $i < count( $string ); $i += 2 )
- $string[$i] = @htmlspecialchars( $string[$i], $quote_style, $charset );
+ for ( $i = 0; $i < count( $string ); $i += 2 )
+ $string[$i] = @htmlspecialchars( $string[$i], $quote_style, $charset );
- $string = implode( '', $string );
- }
+ $string = implode( '', $string );
+ }
- // Backwards compatibility
- if ( 'single' === $_quote_style )
- $string = str_replace( "'", ''', $string );
+ // Backwards compatibility
+ if ( 'single' === $_quote_style )
+ $string = str_replace( "'", ''', $string );
- return $string;
+ return $string;
}
/**
@@ -386,51 +458,53 @@ function yourls_specialchars( $string, $quote_style = ENT_NOQUOTES, $double_enco
* @return string The decoded text without HTML entities.
*/
function yourls_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) {
- $string = (string) $string;
-
- if ( 0 === strlen( $string ) ) {
- return '';
- }
-
- // Don't bother if there are no entities - saves a lot of processing
- if ( strpos( $string, '&' ) === false ) {
- return $string;
- }
-
- // Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value
- if ( empty( $quote_style ) ) {
- $quote_style = ENT_NOQUOTES;
- } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) {
- $quote_style = ENT_QUOTES;
- }
-
- // More complete than get_html_translation_table( HTML_SPECIALCHARS )
- $single = array( ''' => '\'', ''' => '\'' );
- $single_preg = array( '/*39;/' => ''', '/*27;/i' => ''' );
- $double = array( '"' => '"', '"' => '"', '"' => '"' );
- $double_preg = array( '/*34;/' => '"', '/*22;/i' => '"' );
- $others = array( '<' => '<', '<' => '<', '>' => '>', '>' => '>', '&' => '&', '&' => '&', '&' => '&' );
- $others_preg = array( '/*60;/' => '<', '/*62;/' => '>', '/*38;/' => '&', '/*26;/i' => '&' );
-
- if ( $quote_style === ENT_QUOTES ) {
- $translation = array_merge( $single, $double, $others );
- $translation_preg = array_merge( $single_preg, $double_preg, $others_preg );
- } elseif ( $quote_style === ENT_COMPAT || $quote_style === 'double' ) {
- $translation = array_merge( $double, $others );
- $translation_preg = array_merge( $double_preg, $others_preg );
- } elseif ( $quote_style === 'single' ) {
- $translation = array_merge( $single, $others );
- $translation_preg = array_merge( $single_preg, $others_preg );
- } elseif ( $quote_style === ENT_NOQUOTES ) {
- $translation = $others;
- $translation_preg = $others_preg;
- }
-
- // Remove zero padding on numeric entities
- $string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string );
-
- // Replace characters according to translation table
- return strtr( $string, $translation );
+ $string = (string) $string;
+
+ if ( 0 === strlen( $string ) ) {
+ return '';
+ }
+
+ // Don't bother if there are no entities - saves a lot of processing
+ if ( strpos( $string, '&' ) === false ) {
+ return $string;
+ }
+
+ // Match the previous behaviour of _wp_specialchars() when the $quote_style is not an accepted value
+ if ( empty( $quote_style ) ) {
+ $quote_style = ENT_NOQUOTES;
+ } elseif ( !in_array( $quote_style, array( 0, 2, 3, 'single', 'double' ), true ) ) {
+ $quote_style = ENT_QUOTES;
+ }
+
+ // More complete than get_html_translation_table( HTML_SPECIALCHARS )
+ $single = array( ''' => '\'', ''' => '\'' );
+ $single_preg = array( '/*39;/' => ''', '/*27;/i' => ''' );
+ $double = array( '"' => '"', '"' => '"', '"' => '"' );
+ $double_preg = array( '/*34;/' => '"', '/*22;/i' => '"' );
+ $others = array( '<' => '<', '<' => '<', '>' => '>', '>' => '>', '&' => '&', '&' => '&', '&' => '&' );
+ $others_preg = array( '/*60;/' => '<', '/*62;/' => '>', '/*38;/' => '&', '/*26;/i' => '&' );
+
+ $translation = $translation_preg = [];
+
+ if ( $quote_style === ENT_QUOTES ) {
+ $translation = array_merge( $single, $double, $others );
+ $translation_preg = array_merge( $single_preg, $double_preg, $others_preg );
+ } elseif ( $quote_style === ENT_COMPAT || $quote_style === 'double' ) {
+ $translation = array_merge( $double, $others );
+ $translation_preg = array_merge( $double_preg, $others_preg );
+ } elseif ( $quote_style === 'single' ) {
+ $translation = array_merge( $single, $others );
+ $translation_preg = array_merge( $single_preg, $others_preg );
+ } elseif ( $quote_style === ENT_NOQUOTES ) {
+ $translation = $others;
+ $translation_preg = $others_preg;
+ }
+
+ // Remove zero padding on numeric entities
+ $string = preg_replace( array_keys( $translation_preg ), array_values( $translation_preg ), $string );
+
+ // Replace characters according to translation table
+ return strtr( $string, $translation );
}
@@ -443,9 +517,9 @@ function yourls_specialchars_decode( $string, $quote_style = ENT_NOQUOTES ) {
* @return string
*/
function yourls_esc_html( $text ) {
- $safe_text = yourls_check_invalid_utf8( $text );
- $safe_text = yourls_specialchars( $safe_text, ENT_QUOTES );
- return yourls_apply_filter( 'esc_html', $safe_text, $text );
+ $safe_text = yourls_check_invalid_utf8( $text );
+ $safe_text = yourls_specialchars( $safe_text, ENT_QUOTES );
+ return yourls_apply_filter( 'esc_html', $safe_text, $text );
}
/**
@@ -457,9 +531,9 @@ function yourls_esc_html( $text ) {
* @return string
*/
function yourls_esc_attr( $text ) {
- $safe_text = yourls_check_invalid_utf8( $text );
- $safe_text = yourls_specialchars( $safe_text, ENT_QUOTES );
- return yourls_apply_filter( 'esc_attr', $safe_text, $text );
+ $safe_text = yourls_check_invalid_utf8( $text );
+ $safe_text = yourls_specialchars( $safe_text, ENT_QUOTES );
+ return yourls_apply_filter( 'esc_attr', $safe_text, $text );
}
/**
@@ -482,38 +556,41 @@ function yourls_esc_url( $url, $context = 'display', $protocols = array() ) {
// trim first -- see #1931
$url = trim( $url );
- // make sure there's only one 'http://' at the beginning (prevents pasting a URL right after the default 'http://')
- $url = str_replace(
- array( 'http://http://', 'http://https://' ),
- array( 'http://', 'https://' ),
- $url
- );
+ // make sure there's only one 'http://' at the beginning (prevents pasting a URL right after the default 'http://')
+ $url = str_replace(
+ array( 'http://http://', 'http://https://' ),
+ array( 'http://', 'https://' ),
+ $url
+ );
- if ( '' == $url )
- return $url;
+ if ( '' == $url )
+ return $url;
- $original_url = $url;
+ $original_url = $url;
- // force scheme and domain to lowercase - see issues 591 and 1630
+ // force scheme and domain to lowercase - see issues 591 and 1630
$url = yourls_normalize_uri( $url );
- $url = preg_replace( '|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\x80-\\xff]|i', '', $url );
- // Previous regexp in YOURLS was '|[^a-z0-9-~+_.?\[\]\^#=!&;,/:%@$\|*`\'<>"()\\x80-\\xff\{\}]|i'
- // TODO: check if that was it too destructive
+ $url = preg_replace( '|[^a-z0-9-~+_.?#=!&;,/:%@$\|*\'()\[\]\\\\\x80-\\xff]|i', '', $url );
+ // The replace above allows backslashes now, but we only should only allow them after a query string or a fragment identifier
+ $url = yourls_remove_backslashes_before_query_fragment($url);
+
+ // Previous regexp in YOURLS was '|[^a-z0-9-~+_.?\[\]\^#=!&;,/:%@$\|*`\'<>"()\\x80-\\xff\{\}]|i'
+ // TODO: check if that was it too destructive
// If $context is 'safe', an extra step is taken to make sure no CRLF injection is possible.
// To be used when $url can be forged by evil user (eg it's from a $_SERVER variable, a query string, etc..)
- if ( 'safe' == $context ) {
+ if ( 'safe' == $context ) {
$strip = array( '%0d', '%0a', '%0D', '%0A' );
$url = yourls_deep_replace( $strip, $url );
}
- // Replace ampersands and single quotes only when displaying.
- if ( 'display' == $context ) {
- $url = yourls_kses_normalize_entities( $url );
- $url = str_replace( '&', '&', $url );
- $url = str_replace( "'", ''', $url );
- }
+ // Replace ampersands and single quotes only when displaying.
+ if ( 'display' == $context ) {
+ $url = yourls_kses_normalize_entities( $url );
+ $url = str_replace( '&', '&', $url );
+ $url = str_replace( "'", ''', $url );
+ }
// If there's a protocol, make sure it's OK
if( yourls_get_protocol($url) !== '' ) {
@@ -529,9 +606,45 @@ function yourls_esc_url( $url, $context = 'display', $protocols = array() ) {
// I didn't use KSES function kses_bad_protocol() because it doesn't work the way I liked (returns //blah from illegal://blah)
}
- return yourls_apply_filter( 'esc_url', $url, $original_url, $context );
+ return yourls_apply_filter( 'esc_url', $url, $original_url, $context );
}
+/**
+ * Remove backslashes before query string or fragment identifier
+ *
+ * This function removes backslashes before the first ? or #, if any.
+ * If there's no ? or #, all backslashes are removed.
+ * See issue #3802 and PR #3998
+ *
+ * @since 1.10.3
+ * @param string $url URL
+ * @return string URL without backslashes before query string or fragment identifier
+ */
+function yourls_remove_backslashes_before_query_fragment(string $url): string {
+ $posQ = strpos($url, '?');
+ $posH = strpos($url, '#');
+
+ if ($posQ === false && $posH === false) {
+ // no ? or # -> remove all backslashes
+ return str_replace('\\', '', $url);
+ }
+
+ // chose the first of ? or #
+ if ($posQ === false) {
+ $pos = $posH;
+ } elseif ($posH === false) {
+ $pos = $posQ;
+ } else {
+ $pos = min($posQ, $posH);
+ }
+
+ $before = substr($url, 0, $pos);
+ $after = substr($url, $pos);
+
+ $before = str_replace('\\', '', $before);
+
+ return $before . $after;
+}
/**
* Normalize a URI : lowercase scheme and domain, convert IDN to UTF8
@@ -603,7 +716,7 @@ function yourls_normalize_uri( $url ) {
$lower['host'] = mb_strtolower($parts['host']);
/**
* Convert IDN domains to their UTF8 form so that طارق.net and xn--mgbuq0c.net
- * are considered the same. Explicitely mention option and variant to avoid notice
+ * are considered the same. Explicitly mention option and variant to avoid notice
* on PHP 7.2 and 7.3
*/
$lower['host'] = idn_to_utf8($lower['host'], IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46);
@@ -628,12 +741,12 @@ function yourls_normalize_uri( $url ) {
* @return string Escaped text.
*/
function yourls_esc_js( $text ) {
- $safe_text = yourls_check_invalid_utf8( $text );
- $safe_text = yourls_specialchars( $safe_text, ENT_COMPAT );
- $safe_text = preg_replace( '/(x)?0*(?(1)27|39);?/i', "'", stripslashes( $safe_text ) );
- $safe_text = str_replace( "\r", '', $safe_text );
- $safe_text = str_replace( "\n", '\\n', addslashes( $safe_text ) );
- return yourls_apply_filter( 'esc_js', $safe_text, $text );
+ $safe_text = yourls_check_invalid_utf8( $text );
+ $safe_text = yourls_specialchars( $safe_text, ENT_COMPAT );
+ $safe_text = preg_replace( '/(x)?0*(?(1)27|39);?/i', "'", stripslashes( $safe_text ) );
+ $safe_text = str_replace( "\r", '', $safe_text );
+ $safe_text = str_replace( "\n", '\\n', addslashes( $safe_text ) );
+ return yourls_apply_filter( 'esc_js', $safe_text, $text );
}
/**
@@ -645,44 +758,20 @@ function yourls_esc_js( $text ) {
* @return string
*/
function yourls_esc_textarea( $text ) {
- $safe_text = htmlspecialchars( $text, ENT_QUOTES );
- return yourls_apply_filter( 'esc_textarea', $safe_text, $text );
-}
-
-
-/**
-* PHP emulation of JS's encodeURI
-*
-* @link https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI
-* @param $url
-* @return string
-*/
-function yourls_encodeURI( $url ) {
- // Decode URL all the way
- $result = yourls_rawurldecode_while_encoded( $url );
- // Encode once
- $result = strtr( rawurlencode( $result ), array (
- '%3B' => ';', '%2C' => ',', '%2F' => '/', '%3F' => '?', '%3A' => ':', '%40' => '@',
- '%26' => '&', '%3D' => '=', '%2B' => '+', '%24' => '$', '%21' => '!', '%2A' => '*',
- '%27' => '\'', '%28' => '(', '%29' => ')', '%23' => '#',
- ) );
- // @TODO:
- // Known limit: this will most likely break IDN URLs such as http://www.académie-française.fr/
- // To fully support IDN URLs, advocate use of a plugin.
- return yourls_apply_filter( 'encodeURI', $result, $url );
+ $safe_text = htmlspecialchars( $text, ENT_QUOTES );
+ return yourls_apply_filter( 'esc_textarea', $safe_text, $text );
}
/**
* Adds backslashes before letters and before a number at the start of a string. Stolen from WP.
*
* @since 1.6
- *
* @param string $string Value to which backslashes will be added.
* @return string String with backslashes inserted.
*/
function yourls_backslashit($string) {
- $string = preg_replace('/^([0-9])/', '\\\\\\\\\1', $string);
- $string = preg_replace('/([a-z])/i', '\\\\\1', $string);
+ $string = preg_replace('/^([0-9])/', '\\\\\\\\\1', (string)$string);
+ $string = preg_replace('/([a-z])/i', '\\\\\1', (string)$string);
return $string;
}
@@ -696,7 +785,7 @@ function yourls_backslashit($string) {
* @return bool
*/
function yourls_is_rawurlencoded( $string ) {
- return rawurldecode( $string ) != $string;
+ return rawurldecode( $string ) != $string;
}
/**
@@ -710,11 +799,11 @@ function yourls_is_rawurlencoded( $string ) {
* @return string
*/
function yourls_rawurldecode_while_encoded( $string ) {
- $string = rawurldecode( $string );
- if( yourls_is_rawurlencoded( $string ) ) {
- $string = yourls_rawurldecode_while_encoded( $string );
- }
- return $string;
+ $string = rawurldecode( $string );
+ if( yourls_is_rawurlencoded( $string ) ) {
+ $string = yourls_rawurldecode_while_encoded( $string );
+ }
+ return $string;
}
/**
@@ -740,7 +829,7 @@ function yourls_make_bookmarklet( $code ) {
*/
function yourls_get_timestamp( $timestamp ) {
$offset = yourls_get_time_offset();
- $timestamp_offset = $timestamp + ($offset * 3600);
+ $timestamp_offset = (int)$timestamp + ($offset * 3600);
return yourls_apply_filter( 'get_timestamp', $timestamp_offset, $timestamp, $offset );
}
@@ -788,4 +877,3 @@ function yourls_get_date_format( $format ) {
function yourls_get_time_format( $format ) {
return yourls_apply_filter( 'get_time_format', (string)$format );
}
-
diff --git a/includes/functions-html.php b/includes/functions-html.php
index 46432f0c7..aadf6f696 100644
--- a/includes/functions-html.php
+++ b/includes/functions-html.php
@@ -3,18 +3,19 @@
/**
* Display header and logo
*
+ * @return void
*/
function yourls_html_logo() {
- yourls_do_action( 'pre_html_logo' );
- ?>
-
-
+
+
+ ?>
>
-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- ';
- echo join( "\n", yourls_get_debug_log() );
- echo ' ';
- } ?>
-
-
-
-
+
+
+ ';
+ echo join( "\n", yourls_get_debug_log() );
+ echo ' ';
+ } ?>
+
+