diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 275c506a..38cf8c82 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu-22.04 +FROM mcr.microsoft.com/vscode/devcontainers/base:ubuntu-24.04 ADD first-run-notice.txt /usr/local/etc/vscode-dev-containers/first-run-notice.txt diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..f14aa549 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false +indent_size = 2 + +[*.{js,css}] +indent_size = 2 diff --git a/.gitattributes b/.gitattributes index dfe07704..5a0d5e48 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ # Auto detect text files and perform LF normalization -* text=auto +* text=auto eol=lf diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index a04126ee..a2e6e21c 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -27,8 +27,13 @@ jobs: if: ${{ github.event_name != 'pull_request' }} uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '22' + - name: Install prettier and plugin-php - run: npm install --global prettier@2.8.1 @prettier/plugin-php@0.18.9 + run: npm i - name: Lint with Prettier continue-on-error: true diff --git a/.gitignore b/.gitignore index 24ee490f..ea2ef0ba 100644 --- a/.gitignore +++ b/.gitignore @@ -12,9 +12,10 @@ package-lock.json # Environment .env +.php-version DOCKER_ENV docker_tag # IDE .vscode/ -.idea/ \ No newline at end of file +.idea/ diff --git a/.prettierignore b/.prettierignore index 22d0d82f..b5449b69 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1 +1,3 @@ vendor +**/*.min.js +.prettierrc \ No newline at end of file diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..3a7657e1 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,13 @@ +module.exports = { + printWidth: 120, + endOfLine: "auto", + plugins: ["@prettier/plugin-php"], + overrides: [ + { + files: "*.php", + options: { + parser: "php", + }, + }, + ], +}; diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..911749da --- /dev/null +++ b/Dockerfile @@ -0,0 +1,64 @@ +# Use PHP 8.3 (8.4 not supported yet) +FROM php:8.3-apache@sha256:6be4ef702b2dd05352f7e5fe14667696a4ad091c9d2ad9083becbee4300dc3b1 + +# Install system dependencies and PHP extensions in one layer +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + unzip \ + libicu-dev \ + inkscape \ + fonts-dejavu-core \ + curl \ + && docker-php-ext-configure intl \ + && docker-php-ext-install intl \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +# Install Composer +COPY --from=composer/composer:latest-bin@sha256:c9bda63056674836406cacfbbdd8ef770fb4692ac419c967034225213c64e11b /composer /usr/bin/composer + +# Set working directory +WORKDIR /var/www/html + +# Copy composer files and install dependencies +COPY composer.json composer.lock ./ +COPY src/ ./src/ +RUN composer install --no-dev --optimize-autoloader --no-scripts + +# Configure Apache to serve from src/ directory and pass environment variables +RUN a2enmod rewrite headers && \ + echo 'ServerTokens Prod\n\ +ServerSignature Off\n\ +PassEnv TOKEN\n\ +PassEnv WHITELIST\n\ +\n\ + ServerAdmin webmaster@localhost\n\ + DocumentRoot /var/www/html/src\n\ + \n\ + Options -Indexes\n\ + AllowOverride None\n\ + Require all granted\n\ + Header always set Access-Control-Allow-Origin "*"\n\ + Header always set Content-Type "image/svg+xml" "expr=%{REQUEST_URI} =~ m#\\.svg$#i"\n\ + Header always set Content-Security-Policy "default-src 'none'; style-src 'unsafe-inline'; img-src data:;" "expr=%{REQUEST_URI} =~ m#\\.svg$#i"\n\ + Header always set Referrer-Policy "no-referrer-when-downgrade"\n\ + Header always set X-Content-Type-Options "nosniff"\n\ + \n\ + ErrorLog ${APACHE_LOG_DIR}/error.log\n\ + CustomLog ${APACHE_LOG_DIR}/access.log combined\n\ +' > /etc/apache2/sites-available/000-default.conf + +# Set secure permissions +RUN chown -R www-data:www-data /var/www/html && \ + find /var/www/html -type d -exec chmod 755 {} \; && \ + find /var/www/html -type f -exec chmod 644 {} \; + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost/demo/ || exit 1 + +# Expose port +EXPOSE 80 + +# Start Apache +CMD ["apache2-foreground"] diff --git a/README.md b/README.md index dd45b6a9..adb16991 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ The following are the locales that have labels translated in Streak Stats. The ` -
en - English
English 100%
am - አማርኛ
አማርኛ 100%
ar - العربية
العربية 100%
bho - भोजपुरी
भोजपुरी 100%
bn - বাংলা
বাংলা 100%
ca - català
català 100%
ceb - Cebuano
Cebuano 100%
da - dansk
dansk 100%
de - Deutsch
Deutsch 100%
el - Ελληνικά
Ελληνικά 100%
es - español
español 100%
fa - فارسی
فارسی 100%
fil - Filipino
Filipino 100%
fr - français
français 100%
gu - ગુજરાતી
ગુજરાતી 100%
he - עברית
עברית 100%
hi - हिन्दी
हिन्दी 100%
hu - magyar
magyar 100%
id - Indonesia
Indonesia 100%
it - italiano
italiano 100%
ja - 日本語
日本語 100%
jv - Jawa
Jawa 100%
kn - ಕನ್ನಡ
ಕನ್ನಡ 100%
ko - 한국어
한국어 100%
mr - मराठी
मराठी 100%
ms - Melayu
Melayu 100%
ms_ID - Melayu (Indonesia)
Melayu (Indonesia) 100%
my - မြန်မာ
မြန်မာ 100%
ne - नेपाली
नेपाली 100%
nl - Nederlands
Nederlands 100%
no - norsk
norsk 100%
pl - polski
polski 100%
ps - پښتو
پښتو 100%
pt - português
português 100%
pt_BR - português (Brasil)
português (Brasil) 100%
ru - русский
русский 100%
sa - संस्कृत भाषा
संस्कृत भाषा 100%
sd_PK - سنڌي (پاڪستان)
سنڌي (پاڪستان) 100%
sr - српски
српски 100%
su - Basa Sunda
Basa Sunda 100%
sw - Kiswahili
Kiswahili 100%
th - ไทย
ไทย 100%
tr - Türkçe
Türkçe 100%
uk - українська
українська 100%
ur_PK - اردو (پاکستان)
اردو (پاکستان) 100%
vi - Tiếng Việt
Tiếng Việt 100%
yo - Èdè Yorùbá
Èdè Yorùbá 100%
zh_Hans - 中文(简体)
中文(简体) 100%
zh_Hant - 中文(繁體)
中文(繁體) 100%
bg - български
български 86%
ht - créole haïtien
créole haïtien 86%
hy - հայերեն
հայերեն 86%
rw - Kinyarwanda
Kinyarwanda 86%
sv - svenska
svenska 86%
ta - தமிழ்
தமிழ் 86%
+
en - English
English 100%
am - አማርኛ
አማርኛ 100%
ar - العربية
العربية 100%
bho - भोजपुरी
भोजपुरी 100%
bn - বাংলা
বাংলা 100%
ca - català
català 100%
ceb - Cebuano
Cebuano 100%
da - dansk
dansk 100%
de - Deutsch
Deutsch 100%
el - Ελληνικά
Ελληνικά 100%
es - español
español 100%
fa - فارسی
فارسی 100%
fil - Filipino
Filipino 100%
fr - français
français 100%
gu - ગુજરાતી
ગુજરાતી 100%
he - עברית
עברית 100%
hi - हिन्दी
हिन्दी 100%
hu - magyar
magyar 100%
id - Indonesia
Indonesia 100%
it - italiano
italiano 100%
ja - 日本語
日本語 100%
jv - Jawa
Jawa 100%
kn - ಕನ್ನಡ
ಕನ್ನಡ 100%
ko - 한국어
한국어 100%
mal - മലയാളം
മലയാളം 100%
mi - Māori
Māori 100%
mr - मराठी
मराठी 100%
ms - Melayu
Melayu 100%
ms_ID - Melayu (Indonesia)
Melayu (Indonesia) 100%
my - မြန်မာ
မြန်မာ 100%
ne - नेपाली
नेपाली 100%
nl - Nederlands
Nederlands 100%
no - norsk
norsk 100%
pa - ਪੰਜਾਬੀ
ਪੰਜਾਬੀ 100%
pl - polski
polski 100%
ps - پښتو
پښتو 100%
pt - português
português 100%
pt_BR - português (Brasil)
português (Brasil) 100%
ru - русский
русский 100%
sa - संस्कृत भाषा
संस्कृत भाषा 100%
sd_PK - سنڌي (پاڪستان)
سنڌي (پاڪستان) 100%
sr_Cyrl - српски (ћирилица)
српски (ћирилица) 100%
sr_Latn - srpski (latinica)
srpski (latinica) 100%
su - Basa Sunda
Basa Sunda 100%
sv - svenska
svenska 100%
sw - Kiswahili
Kiswahili 100%
ta - தமிழ்
தமிழ் 100%
th - ไทย
ไทย 100%
tr - Türkçe
Türkçe 100%
uk - українська
українська 100%
ur_PK - اردو (پاکستان)
اردو (پاکستان) 100%
vi - Tiếng Việt
Tiếng Việt 100%
yo - Èdè Yorùbá
Èdè Yorùbá 100%
zh_Hans - 中文(简体)
中文(简体) 100%
zh_Hant - 中文(繁體)
中文(繁體) 100%
bg - български
български 86%
ht - créole haïtien
créole haïtien 86%
hy - հայերեն
հայերեն 86%
rw - Kinyarwanda
Kinyarwanda 86%
@@ -147,7 +147,7 @@ The longest streak is the highest number of consecutive days on which you have m The current streak is the number of consecutive days ending with the current day on which you have made at least one contribution. If you have made a contribution today, it will be counted towards the current streak, however, if you have not made a contribution today, the streak will only count days before today so that your streak will not be zero. -> [!NOTE] +> [!NOTE] > You may need to wait up to 24 hours for new contributions to show up ([Learn how contributions are counted](https://docs.github.com/articles/why-are-my-contributions-not-showing-up-on-my-profile)) ## 📤 Deploying it on your own @@ -164,7 +164,7 @@ The Inkscape dependency is required for PNG rendering, as well as Segoe UI font Vercel is the recommended option for hosting the files since it is **free** and easy to set up. Watch the video below or expand the instructions to learn how to deploy to Vercel. -> [!NOTE] +> [!NOTE] > PNG mode is not supported since Inkscape will not be installed but the default SVG mode will work. ### 📺 [Click here for a video tutorial on how to self-host on Vercel](https://www.youtube.com/watch?v=maoXtlb8t44) @@ -210,14 +210,15 @@ Vercel is the recommended option for hosting the files since it is **free** and 9. Scroll to the bottom of the page and click on **"Generate token"** 10. Visit the Vercel dashboard at and select your project. Then, click on **"Settings"** and choose **"Environment Variables"**. 11. Add a new environment variable with the key `TOKEN` and the value as the token you generated in step 9, then save your changes -12. To apply the new environment variable, you need to redeploy the app. Run `vercel --prod` to deploy the app to production. +12. (Optional) You can also set the `WHITELIST` environment variable to restrict which GitHub usernames can be accessed through the service. Provide the usernames as a comma-separated list, for example: `user1,user2,user3`. If the variable is not set, information can be requested for any GitHub user. +13. To apply the new environment variable(s), you need to redeploy the app. Run `vercel --prod` to deploy the app to production. ![image](https://user-images.githubusercontent.com/20955511/209588756-8bf5b0cd-9aa6-41e8-909c-97bf41e525b3.png) -> ⚠️ **Note** +> ⚠️ **Note** > To set up automatic Vercel deployments from GitHub, make sure to turn **off** "Include source files outside of the Root Directory" in the General settings and use `vercel` as the production branch in the Git settings. -> ⚠️ **Note** +> ⚠️ **Note** > If you receive an error related to libssl or Node 20.x, you can fix this by opening your Vercel project settings and changing the Node.js version to 18.x. > > ![image](https://github.com/DenverCoder1/github-readme-streak-stats/assets/20955511/5fb18fb5-debe-4620-9c8b-193ab442a617) @@ -230,9 +231,9 @@ Heroku is another great option for hosting the files. All features are supported
Instructions for deploying to Heroku (Paid) - + ### Step-by-step instructions for deploying to Heroku - + 1. Sign in to **Heroku** or create a new account at 2. Visit [this link](https://github.com/settings/tokens/new?description=GitHub%20Readme%20Streak%20Stats) to create a new Personal Access Token (no scopes required) 3. Scroll to the bottom and click **"Generate token"** @@ -244,8 +245,9 @@ Heroku is another great option for hosting the files. All features are supported ![heroku config variables](https://user-images.githubusercontent.com/20955511/136292022-a8d9b3b5-d7d8-4a5e-a049-8d23b51ce9d7.png) -6. Click **"Deploy App"** at the end of the form -7. Once the app is deployed, you can use `.herokuapp.com` in place of `streak-stats.demolab.com` +6. (Optional) You can also set the `WHITELIST` Config Var to restrict which GitHub usernames can be accessed through the service. Provide the usernames as a comma-separated list, for example: `user1,user2,user3`. If the variable is not set, information can be requested for any GitHub user. +7. Click **"Deploy App"** at the end of the form +8. Once the app is deployed, you can use `.herokuapp.com` in place of `streak-stats.demolab.com`
@@ -253,6 +255,49 @@ Heroku is another great option for hosting the files. All features are supported You can transfer the files to any webserver using FTP or other means, then refer to [CONTRIBUTING.md](/CONTRIBUTING.md) for installation steps. +### 🐳 Docker + +Docker is a great option for self-hosting with full control over your environment. All features are supported including PNG rendering with Inkscape. Expand the instructions below to learn how to deploy with Docker. + +
+ Instructions for deploying with Docker + +### Step-by-step instructions for deploying with Docker + +1. Clone the repository: + + ```bash + git clone https://github.com/DenverCoder1/github-readme-streak-stats.git + cd github-readme-streak-stats + ``` + +2. Visit https://github.com/settings/tokens/new?description=GitHub%20Readme%20Streak%20Stats to create a new Personal Access Token (no scopes required) + +3. Scroll to the bottom and click "Generate token" + +4. Build the Docker image: + + ```bash + docker build -t streak-stats . + ``` + +5. Run the container with your GitHub token: + + ```bash + docker run -d -p 8080:80 -e TOKEN=your_github_token_here streak-stats + ``` + +6. You can also optionally set the `WHITELIST` environment variable to restrict which GitHub usernames can be accessed through the service. If the `WHITELIST` variable is not set, information can be requested for any GitHub user. + Provide the usernames as a comma-separated list, for example: + + ```bash + docker run -d -p 8080:80 -e TOKEN=your_github_token_here -e WHITELIST=user1,user2,user3 streak-stats + ``` + +7. Visit http://localhost:8080 to access your self-hosted instance + +
+ [hspace]: https://user-images.githubusercontent.com/20955511/136058102-b79570bc-4912-4369-b664-064a0ada8588.png [verceldeploy]: https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FDenverCoder1%2Fgithub-readme-streak-stats%2Ftree%2Fvercel&env=TOKEN&envDescription=GitHub%20Personal%20Access%20Token%20(no%20scopes%20required)&envLink=https%3A%2F%2Fgithub.com%2Fsettings%2Ftokens%2Fnew%3Fdescription%3DGitHub%2520Readme%2520Streak%2520Stats&project-name=streak-stats&repository-name=github-readme-streak-stats [herokudeploy]: https://heroku.com/deploy?template=https://github.com/DenverCoder1/github-readme-streak-stats/tree/main diff --git a/composer.json b/composer.json index 82a95b97..63e189c3 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "php -S localhost:8000 -t src" ], "test": "./vendor/bin/phpunit --testdox tests", - "lint": "prettier --check *.md **/*.{php,md,js,css} !**/*.min.js --print-width 120", - "lint-fix": "prettier --write *.md **/*.{php,md,js,css} !**/*.min.js --print-width 120" + "lint": "npx prettier --check *.md **/*.{php,md,js,css}", + "lint-fix": "npx prettier --write *.md **/*.{php,md,js,css}" } } diff --git a/composer.lock b/composer.lock index 5fd881a5..ed0d9740 100644 --- a/composer.lock +++ b/composer.lock @@ -70,16 +70,16 @@ }, { "name": "phpoption/phpoption", - "version": "1.9.3", + "version": "1.9.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/php-option.git", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54" + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54", - "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54", + "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", + "reference": "638a154f8d4ee6a5cfa96d6a34dfbe0cffa9566d", "shasum": "" }, "require": { @@ -87,7 +87,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28" + "phpunit/phpunit": "^8.5.44 || ^9.6.25 || ^10.5.53 || ^11.5.34" }, "type": "library", "extra": { @@ -129,7 +129,7 @@ ], "support": { "issues": "https://github.com/schmittjoh/php-option/issues", - "source": "https://github.com/schmittjoh/php-option/tree/1.9.3" + "source": "https://github.com/schmittjoh/php-option/tree/1.9.4" }, "funding": [ { @@ -141,24 +141,24 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:41:07+00:00" + "time": "2025-08-21T11:53:16+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.30.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", - "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "provide": { "ext-ctype": "*" @@ -169,8 +169,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -204,7 +204,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.33.0" }, "funding": [ { @@ -215,29 +215,34 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2024-09-09T11:45:10+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.30.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", - "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", "shasum": "" }, "require": { - "php": ">=7.1" + "ext-iconv": "*", + "php": ">=7.2" }, "provide": { "ext-mbstring": "*" @@ -248,8 +253,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -284,7 +289,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" }, "funding": [ { @@ -295,35 +300,39 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-06-19T12:30:46+00:00" + "time": "2024-12-23T08:48:59+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.30.0", + "version": "v1.33.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", - "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -364,7 +373,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -375,25 +384,29 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-05-31T15:07:36+00:00" + "time": "2025-01-02T08:10:11+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2" + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2", - "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/24ac4c74f91ee2c193fa1aaa5c249cb0822809af", + "reference": "24ac4c74f91ee2c193fa1aaa5c249cb0822809af", "shasum": "" }, "require": { @@ -452,7 +465,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.2" }, "funding": [ { @@ -464,22 +477,22 @@ "type": "tidelift" } ], - "time": "2024-07-20T21:52:34+00:00" + "time": "2025-04-30T23:37:27+00:00" } ], "packages-dev": [ { "name": "myclabs/deep-copy", - "version": "1.12.1", + "version": "1.13.4", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", - "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/07d290f0c47959fd5eed98c95ee5602db07e0b6a", + "reference": "07d290f0c47959fd5eed98c95ee5602db07e0b6a", "shasum": "" }, "require": { @@ -518,7 +531,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.4" }, "funding": [ { @@ -526,20 +539,20 @@ "type": "tidelift" } ], - "time": "2024-11-08T17:47:46+00:00" + "time": "2025-08-01T08:46:24+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -558,7 +571,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.x-dev" } }, "autoload": { @@ -582,9 +595,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "phar-io/manifest", @@ -706,23 +719,23 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.8", + "version": "11.0.11", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", - "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", + "reference": "4f7722aa9a7b76aa775e2d9d4e95d1ea16eeeef4", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^5.3.1", + "nikic/php-parser": "^5.4.0", "php": ">=8.2", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-text-template": "^4.0.1", @@ -734,7 +747,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.5.0" + "phpunit/phpunit": "^11.5.2" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -772,15 +785,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.11" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2024-12-11T12:34:27+00:00" + "time": "2025-08-27T14:37:49+00:00" }, { "name": "phpunit/php-file-iterator", @@ -1029,16 +1054,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.5.7", + "version": "11.5.35", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e1cb706f019e2547039ca2c839898cd5f557ee5d" + "reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e1cb706f019e2547039ca2c839898cd5f557ee5d", - "reference": "e1cb706f019e2547039ca2c839898cd5f557ee5d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/d341ee94ee5007b286fc7907b383aae6b5b3cc91", + "reference": "d341ee94ee5007b286fc7907b383aae6b5b3cc91", "shasum": "" }, "require": { @@ -1048,24 +1073,24 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.1", + "myclabs/deep-copy": "^1.13.4", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", - "phpunit/php-code-coverage": "^11.0.8", + "phpunit/php-code-coverage": "^11.0.11", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-invoker": "^5.0.1", "phpunit/php-text-template": "^4.0.1", "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", - "sebastian/code-unit": "^3.0.2", - "sebastian/comparator": "^6.3.0", + "sebastian/code-unit": "^3.0.3", + "sebastian/comparator": "^6.3.2", "sebastian/diff": "^6.0.2", - "sebastian/environment": "^7.2.0", + "sebastian/environment": "^7.2.1", "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", - "sebastian/type": "^5.1.0", + "sebastian/type": "^5.1.3", "sebastian/version": "^5.0.2", "staabm/side-effects-detector": "^1.0.5" }, @@ -1110,7 +1135,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.7" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.35" }, "funding": [ { @@ -1121,12 +1146,20 @@ "url": "https://github.com/sebastianbergmann", "type": "github" }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, { "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", "type": "tidelift" } ], - "time": "2025-02-06T16:10:05+00:00" + "time": "2025-08-28T05:13:54+00:00" }, { "name": "sebastian/cli-parser", @@ -1187,16 +1220,16 @@ }, { "name": "sebastian/code-unit", - "version": "3.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", - "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/54391c61e4af8078e5b276ab082b6d3c54c9ad64", + "reference": "54391c61e4af8078e5b276ab082b6d3c54c9ad64", "shasum": "" }, "require": { @@ -1232,7 +1265,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", "security": "https://github.com/sebastianbergmann/code-unit/security/policy", - "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.3" }, "funding": [ { @@ -1240,7 +1273,7 @@ "type": "github" } ], - "time": "2024-12-12T09:59:06+00:00" + "time": "2025-03-19T07:56:08+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -1300,16 +1333,16 @@ }, { "name": "sebastian/comparator", - "version": "6.3.0", + "version": "6.3.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115" + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/d4e47a769525c4dd38cea90e5dcd435ddbbc7115", - "reference": "d4e47a769525c4dd38cea90e5dcd435ddbbc7115", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85c77556683e6eee4323e4c5468641ca0237e2e8", + "reference": "85c77556683e6eee4323e4c5468641ca0237e2e8", "shasum": "" }, "require": { @@ -1328,7 +1361,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.2-dev" + "dev-main": "6.3-dev" } }, "autoload": { @@ -1368,15 +1401,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.0" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.3.2" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/comparator", + "type": "tidelift" } ], - "time": "2025-01-06T10:28:19+00:00" + "time": "2025-08-10T08:07:46+00:00" }, { "name": "sebastian/complexity", @@ -1505,23 +1550,23 @@ }, { "name": "sebastian/environment", - "version": "7.2.0", + "version": "7.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", - "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/a5c75038693ad2e8d4b6c15ba2403532647830c4", + "reference": "a5c75038693ad2e8d4b6c15ba2403532647830c4", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "suggest": { "ext-posix": "*" @@ -1557,15 +1602,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.1" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/environment", + "type": "tidelift" } ], - "time": "2024-07-03T04:54:44+00:00" + "time": "2025-05-21T11:55:47+00:00" }, { "name": "sebastian/exporter", @@ -1881,23 +1938,23 @@ }, { "name": "sebastian/recursion-context", - "version": "6.0.2", + "version": "6.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", - "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/f6458abbf32a6c8174f8f26261475dc133b3d9dc", + "reference": "f6458abbf32a6c8174f8f26261475dc133b3d9dc", "shasum": "" }, "require": { "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^11.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { @@ -1933,28 +1990,40 @@ "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/recursion-context", + "type": "tidelift" } ], - "time": "2024-07-03T05:10:34+00:00" + "time": "2025-08-13T04:42:22+00:00" }, { "name": "sebastian/type", - "version": "5.1.0", + "version": "5.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", - "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/f77d2d4e78738c98d9a68d2596fe5e8fa380f449", + "reference": "f77d2d4e78738c98d9a68d2596fe5e8fa380f449", "shasum": "" }, "require": { @@ -1990,15 +2059,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/type/issues", "security": "https://github.com/sebastianbergmann/type/security/policy", - "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/type/tree/5.1.3" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/sebastian/type", + "type": "tidelift" } ], - "time": "2024-09-17T13:12:04+00:00" + "time": "2025-08-09T06:55:48+00:00" }, { "name": "sebastian/version", @@ -2167,5 +2248,5 @@ "ext-intl": "*" }, "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/docs/themes.md b/docs/themes.md index ad476960..ced17713 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -167,6 +167,11 @@ Note: Theme names provided are case-insensitive and any use of underscores will | `rust-ferris-dark` | ![image](https://github.com/user-attachments/assets/05e3f9ac-708d-415d-990f-ede3d0a84bab) | | `cyber-streakglow` | ![image](https://github.com/user-attachments/assets/8c6108e1-f3a1-4653-9f68-08ed6dcfc498) | | `vitesse` | ![image](https://github.com/user-attachments/assets/baa2fa20-36ea-4158-befc-79c21f102f87) | +| `nord-aurora` | ![image](https://github.com/user-attachments/assets/d61bf5c3-66f2-4c02-bd9d-30bf1be47c97) | +| `dark-aura` | ![Image](https://github.com/user-attachments/assets/14889d0e-26db-4fa6-8026-6312c9b4636e) | +| `everforest-dark` | ![image](https://github.com/user-attachments/assets/45a4e0a0-d330-4233-9d76-89003e59bb31) | +| `everforest-light` | ![image](https://github.com/user-attachments/assets/592466c0-5a67-48cc-adf0-f8a21ca891b6) | +| `oceanic-next` | ![image](https://github.com/user-attachments/assets/e0182770-a511-42b6-a40b-644317268a0f) | ### Can't find the theme you like? diff --git a/package.json b/package.json index fb08e235..628593a8 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "engines": { - "node": "18.x" + "node": "22.x" }, "devDependencies": { - "@prettier/plugin-php": "^0.18.8", - "prettier": "^2.6.2" + "@prettier/plugin-php": "^0.24.0", + "prettier": "^3.6.2" } } diff --git a/scripts/translation-progress.php b/scripts/translation-progress.php index 30289d27..7d9c58b7 100644 --- a/scripts/translation-progress.php +++ b/scripts/translation-progress.php @@ -123,6 +123,6 @@ function updateReadme(string $path, string $start, string $end, string $content) __DIR__ . "/../README.md", "", "", - $badges + $badges, ); exit($update === false ? 1 : 0); diff --git a/src/card.php b/src/card.php index b1532e83..501f80cc 100644 --- a/src/card.php +++ b/src/card.php @@ -27,7 +27,7 @@ function formatDate(string $dateString, string|null $format, string $locale): st $locale, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE, - pattern: $pattern + pattern: $pattern, ); $formatted = $dateFormatter->format($date); } @@ -44,7 +44,7 @@ function formatDate(string $dateString, string|null $format, string $locale): st $locale, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE, - pattern: $pattern + pattern: $pattern, ); $formatted = $dateFormatter->format($date); } @@ -75,7 +75,7 @@ function translateDays(array $days, string $locale): array $locale, IntlDateFormatter::NONE, IntlDateFormatter::NONE, - pattern: $pattern + pattern: $pattern, ); $translatedDays = []; foreach ($days as $day) { @@ -250,7 +250,7 @@ function splitLines(string $text, int $maxChars, int $line1Offset): string return preg_replace( "/^(.*)\n(.*)/", "$1$2", - $text + $text, ); } @@ -752,7 +752,7 @@ function ($matches) { $opacity = $result["opacity"]; return "{$attribute}='{$color}' {$opacityAttribute}='{$opacity}'"; }, - $svg + $svg, ); return $svg; diff --git a/src/demo/css/style.css b/src/demo/css/style.css index f57b550d..852763a6 100644 --- a/src/demo/css/style.css +++ b/src/demo/css/style.css @@ -44,7 +44,10 @@ html { body { background: var(--background); - font-family: Segoe UI, Ubuntu, sans-serif; + font-family: + Segoe UI, + Ubuntu, + sans-serif; padding-top: 10px; color: var(--text); } @@ -105,7 +108,9 @@ h2 { border-radius: 6px; cursor: pointer; font-family: inherit; - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24); + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.12), + 0 1px 2px rgba(0, 0, 0, 0.24); transition: 0.2s ease-in-out; } @@ -115,7 +120,9 @@ h2 { .btn:hover { background-color: var(--blue-dark); - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16), 0 3px 6px rgba(0, 0, 0, 0.23); + box-shadow: + 0 3px 6px rgba(0, 0, 0, 0.16), + 0 3px 6px rgba(0, 0, 0, 0.23); } .btn:disabled { diff --git a/src/demo/css/toggle-dark.css b/src/demo/css/toggle-dark.css index 1fe526f3..227a54ed 100644 --- a/src/demo/css/toggle-dark.css +++ b/src/demo/css/toggle-dark.css @@ -11,12 +11,16 @@ a.darkmode { justify-content: center; border-radius: 50%; border: 2px solid var(--border); - box-shadow: 0 0 3px rgb(0 0 0 / 12%), 0 1px 2px rgb(0 0 0 / 24%); + box-shadow: + 0 0 3px rgb(0 0 0 / 12%), + 0 1px 2px rgb(0 0 0 / 24%); transition: 0.2s ease-in box-shadow; } a.darkmode:hover { - box-shadow: 0 0 6px rgb(0 0 0 / 16%), 0 3px 6px rgb(0 0 0 / 23%); + box-shadow: + 0 0 6px rgb(0 0 0 / 16%), + 0 3px 6px rgb(0 0 0 / 23%); } @media only screen and (max-width: 600px) { diff --git a/src/demo/index.php b/src/demo/index.php index 62f02c4e..81e97011 100644 --- a/src/demo/index.php +++ b/src/demo/index.php @@ -22,7 +22,7 @@ function camelToSkewer(string $str): string function ($matches) { return "-" . strtolower($matches[0]); }, - $str + $str, ); } ?> diff --git a/src/demo/js/accordion.js b/src/demo/js/accordion.js index 78e4e997..48e4deab 100644 --- a/src/demo/js/accordion.js +++ b/src/demo/js/accordion.js @@ -55,7 +55,7 @@ class Accordion { { duration: 400, easing: "ease-out", - } + }, ); // When the animation is complete, call onAnimationFinish() this.animation.onfinish = () => this.onAnimationFinish(false); @@ -93,7 +93,7 @@ class Accordion { { duration: 400, easing: "ease-out", - } + }, ); // When the animation is complete, call onAnimationFinish() this.animation.onfinish = () => this.onAnimationFinish(true); diff --git a/src/demo/js/script.js b/src/demo/js/script.js index 8239318a..465614a5 100644 --- a/src/demo/js/script.js +++ b/src/demo/js/script.js @@ -143,7 +143,7 @@ const preview = { format: "hexa", onChange: `preview.pickerChange(this, '${color1.id}')`, onInput: `preview.pickerChange(this, '${color1.id}')`, - }) + }), ); const color2 = document.createElement("input"); color2.className = "param jscolor"; @@ -154,7 +154,7 @@ const preview = { format: "hexa", onChange: `preview.pickerChange(this, '${color2.id}')`, onInput: `preview.pickerChange(this, '${color2.id}')`, - }) + }), ); rotate.name = color1.name = color2.name = propertyName; color1.value = color1Value; @@ -484,5 +484,5 @@ window.addEventListener( // update previews preview.update(); }, - false + false, ); diff --git a/src/stats.php b/src/stats.php index 6511758c..902eb453 100644 --- a/src/stats.php +++ b/src/stats.php @@ -2,6 +2,8 @@ declare(strict_types=1); +require_once "whitelist.php"; + /** * Build a GraphQL query for a contribution graph * @@ -121,6 +123,10 @@ function executeContributionGraphRequests(string $user, array $years): array */ function getContributionGraphs(string $user, ?int $startingYear = null): array { + if (!isWhitelisted($user)) { + throw new InvalidArgumentException("User not in whitelist.", 403); + } + // get the list of years the user has contributed and the current year's contribution graph $currentYear = intval(date("Y")); $responses = executeContributionGraphRequests($user, [$currentYear]); @@ -210,7 +216,7 @@ function removeGitHubToken(string $token): void if (empty($GLOBALS["ALL_TOKENS"])) { throw new AssertionError( "We are being rate-limited! Check git.io/streak-ratelimit for details.", - 429 + 429, ); } } @@ -287,7 +293,7 @@ function normalizeDays(array $days): array $dayOfWeek = substr(ucfirst(strtolower(trim($dayOfWeek))), 0, 3); // return day if valid, otherwise return null return in_array($dayOfWeek, ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]) ? $dayOfWeek : null; - }, $days) + }, $days), ); } diff --git a/src/themes.php b/src/themes.php index e6fc193d..0e1972e5 100644 --- a/src/themes.php +++ b/src/themes.php @@ -2069,4 +2069,69 @@ "dates" => "#BD976A", "excludeDaysLabel" => "#758575DD", ], + "everforest-dark" => [ + "background" => "#2D353B", + "border" => "#4F585E", + "stroke" => "#4F585E", + "ring" => "#A7C080", + "fire" => "#A7C080", + "currStreakNum" => "#D3C6AA", + "sideNums" => "#A7C080", + "currStreakLabel" => "#D3C6AA", + "sideLabels" => "#A7C080", + "dates" => "#9DA9A0", + "excludeDaysLabel" => "#9DA9A0", + ], + "nord-aurora" => [ + "background" => "#4C566A", + "border" => "#8FBCBB", + "stroke" => "#D8DEE9", + "ring" => "#A3BE8C", + "fire" => "#BF616A", + "currStreakNum" => "#A3BE8C", + "sideNums" => "#B48EAD", + "currStreakLabel" => "#EBCB8B", + "sideLabels" => "#D08770", + "dates" => "#88C0D0", + "excludeDaysLabel" => "#81A1C1", + ], + "dark-aura" => [ + "background" => "#760A11", + "border" => "#310C69C5", + "stroke" => "#FF1D5E", + "ring" => "#C1184E", + "fire" => "#EB4511", + "currStreakNum" => "#EB5454", + "sideNums" => "#EB5454", + "currStreakLabel" => "#FF8F62", + "sideLabels" => "#FF8F62", + "dates" => "#EB5454", + "excludeDaysLabel" => "#758575DD", + ], + "everforest-light" => [ + "background" => "#F2F4EF", + "border" => "#C7CCC2", + "stroke" => "#C7CCC2", + "ring" => "#7F9C6F", + "fire" => "#7F9C6F", + "currStreakNum" => "#55674E", + "sideNums" => "#7F9C6F", + "currStreakLabel" => "#55674E", + "sideLabels" => "#7F9C6F", + "dates" => "#8B9286", + "excludeDaysLabel" => "#8B9286", + ], + "oceanic-next" => [ + "background" => "#1B2B34", + "border" => "#343D46", + "stroke" => "#4F5B66", + "ring" => "#6699CC", + "fire" => "#EC5F67", + "currStreakNum" => "#99C794", + "sideNums" => "#6699CC", + "currStreakLabel" => "#FAC863", + "sideLabels" => "#5FB3B3", + "dates" => "#A7ADBA", + "excludeDaysLabel" => "#A7ADBA", + ], ]; diff --git a/src/translations.php b/src/translations.php index 260f89d4..e66bb4f4 100644 --- a/src/translations.php +++ b/src/translations.php @@ -280,7 +280,26 @@ "Week Streak" => "주간 연속 기여 수", "Longest Week Streak" => "최장 주간 연속 기여 수", "Present" => "현재", - "Excluding {days}" => "{days} 제외하고", + "Excluding {days}" => "{days}를 제외하고", + ], + "mal" => [ + "Total Contributions" => "മൊത്തം സംഭാവനകൾ", + "Current Streak" => "നിലവിലെ സ്ട്രീക്ക്", + "Longest Streak" => "ഏറ്റവും ദൈർഘ്യമേറിയ സ്ട്രീക്ക്", + "Week Streak" => "പ്രതിവാര സ്ട്രീക്ക്", + "Longest Week Streak" => "ദൈർഘ്യമേറിയ ആഴ്ച സ്‌ട്രീക്ക്", + "Present" => "ഇപ്പം", + "Excluding {days}" => "{days} ഒഴികെ", + "comma_separator" => "、", + ], + "mi" => [ + "Total Contributions" => "Tapeke Tākoha", + "Current Streak" => "Raupapa Nāianei", + "Longest Streak" => "Raupapa Roa Rawa", + "Week Streak" => "Raupapa Wiki", + "Longest Week Streak" => "Raupapa Wiki Roa Rawa", + "Present" => "O Nāianei", + "Excluding {days}" => "Haunga {ngā rā}", ], "mr" => [ "Total Contributions" => "एकूण योगदान", @@ -345,6 +364,15 @@ "Present" => "I dag", "Excluding {days}" => "Ekskluderer {days}", ], + "pa" => [ + "Total Contributions" => "ਕੁੱਲ ਯੋਗਦਾਨ", + "Current Streak" => "ਮੌਜੂਦਾ ਲਗਾਤਾਰ ਦਿਨ", + "Longest Streak" => "ਸਭ ਤੋਂ ਲੰਬੀ ਲਗਾਤਾਰ ਸਿਰੀਂ", + "Week Streak" => "ਹਫ਼ਤਾ ਲਗਾਤਾਰ ਸਿਰੀਂ", + "Longest Week Streak" => "ਸਭ ਤੋਂ ਲੰਬੀ ਹਫ਼ਤਾਵਾਰੀ ਸਿਰੀਂ", + "Present" => "ਮੌਜੂਦ", + "Excluding {days}" => "{days} ਨੂੰ ਛੱਡ ਕੇ", + ], "pl" => [ "Total Contributions" => "Suma Kontrybucji", "Current Streak" => "Aktualna Seria", @@ -420,15 +448,25 @@ "Excluding {days}" => "نڪتل {days}", "comma_separator" => "، ", ], - "sr" => [ - "Total Contributions" => "Укупно додавања", + "sr" => "sr_Cyrl", + "sr_Cyrl" => [ + "Total Contributions" => "Укупно доприноса", "Current Streak" => "Тренутна серија", "Longest Streak" => "Најдужа серија", "Week Streak" => "Недељна серија", - "Longest Week Streak" => "Најдужа недељена серија", + "Longest Week Streak" => "Најдужа недељна серија", "Present" => "Данас", "Excluding {days}" => "Искључујући {days}", ], + "sr_Latn" => [ + "Total Contributions" => "Ukupno doprinosa", + "Current Streak" => "Trenutna serija", + "Longest Streak" => "Najduža serija", + "Week Streak" => "Nedeljna serija", + "Longest Week Streak" => "Najduža nedeljna serija", + "Present" => "Danas", + "Excluding {days}" => "Isključujući {days}", + ], "su" => [ "Total Contributions" => "Total Kontribusi", "Current Streak" => "Aksi Ayeuna", @@ -445,6 +483,7 @@ "Week Streak" => "Antal veckor i rad", "Longest Week Streak" => "Längst antal veckor i rad", "Present" => "Just nu", + "Excluding {days}" => "Utom {dagar}", ], "sw" => [ "Total Contributions" => "Jumla ya Michango", @@ -462,6 +501,7 @@ "Week Streak" => "வார\nபங்களிப்புகள்", "Longest Week Streak" => "நீண்ட வார\nபங்களிப்புகள்", "Present" => "இன்றுவரை", + "Excluding {days}" => "{days} தவிர", ], "th" => [ "Total Contributions" => "คอนทริบิ้วต์ทั้งหมด", diff --git a/src/whitelist.php b/src/whitelist.php new file mode 100644 index 00000000..21114834 --- /dev/null +++ b/src/whitelist.php @@ -0,0 +1,13 @@ +assertEquals( array_diff_key($colors, $this->defaultTheme), [], - "The theme '$theme' contains invalid parameters." + "The theme '$theme' contains invalid parameters.", ); # check that no parameters are missing and all values are valid foreach (array_keys($this->defaultTheme) as $param) { @@ -85,7 +85,7 @@ public function testThemesHaveValidParameters(): void $this->assertMatchesRegularExpression( $backgroundRegex, $colors[$param], - "The parameter '$param' of '$theme' is not a valid background value." + "The parameter '$param' of '$theme' is not a valid background value.", ); continue; } @@ -93,13 +93,13 @@ public function testThemesHaveValidParameters(): void $this->assertMatchesRegularExpression( $hexRegex, strtoupper($colors[$param]), - "The parameter '$param' of '$theme' is not a valid hex color." + "The parameter '$param' of '$theme' is not a valid hex color.", ); // check that the key is a valid hex color in uppercase $this->assertMatchesRegularExpression( $hexRegex, $colors[$param], - "The parameter '$param' of '$theme' should not contain lowercase letters." + "The parameter '$param' of '$theme' should not contain lowercase letters.", ); } } @@ -251,7 +251,7 @@ public function testAllThemeNamesNormalized(): void $this->assertEquals( $theme, $normalized, - "Theme name '$theme' is not normalized. It should contain only lowercase letters, numbers, and dashes. Consider renaming it to '$normalized'." + "Theme name '$theme' is not normalized. It should contain only lowercase letters, numbers, and dashes. Consider renaming it to '$normalized'.", ); } } diff --git a/tests/RenderTest.php b/tests/RenderTest.php index a551736a..6fd8dcc9 100644 --- a/tests/RenderTest.php +++ b/tests/RenderTest.php @@ -112,7 +112,7 @@ public function testBorderRadius(): void $this->assertStringContainsString("", $render); $this->assertStringContainsString( "", - $render + $render, ); } @@ -126,19 +126,19 @@ public function testSplitLines(): void // Check label that is too long, split $this->assertEquals( "Chuỗi đóng góp hiệntại", - splitLines("Chuỗi đóng góp hiện tại", 22, -9) + splitLines("Chuỗi đóng góp hiện tại", 22, -9), ); // Check label with manually inserted line break, split $this->assertEquals( "Chuỗi đóng góphiện tại", - splitLines("Chuỗi đóng góp\nhiện tại", 22, -9) + splitLines("Chuỗi đóng góp\nhiện tại", 22, -9), ); // Check date range label, no split $this->assertEquals("Mar 28, 2019 – Apr 12, 2019", splitLines("Mar 28, 2019 – Apr 12, 2019", 28, 0)); // Check date range label that is too long, split $this->assertEquals( "19 de dez. de 2021- 14 de mar.", - splitLines("19 de dez. de 2021 - 14 de mar.", 24, 0) + splitLines("19 de dez. de 2021 - 14 de mar.", 24, 0), ); } @@ -194,7 +194,7 @@ public function testGradientBackground(): void $this->assertStringContainsString("fill='url(#gradient)'", $render); $this->assertStringContainsString( "", - $render + $render, ); } @@ -208,7 +208,7 @@ public function testGradientBackgroundWithMoreThan2Colors(): void $this->assertStringContainsString("fill='url(#gradient)'", $render); $this->assertStringContainsString( "", - $render + $render, ); } @@ -261,14 +261,13 @@ public function testFirstAndThirdColumnsSwappedWhenDirectionIsRtl(): void { $this->testParams["locale"] = "he"; $render = generateOutput($this->testStats, $this->testParams)["body"]; - $renderCollapsedSpaces = preg_replace("/(\s)\s*/", '$1', $render); - $this->assertStringContainsString( - "\n", - $renderCollapsedSpaces + $this->assertMatchesRegularExpression( + "/\\s*/", + $render, ); - $this->assertStringContainsString( - "\n", - $renderCollapsedSpaces + $this->assertMatchesRegularExpression( + "/\\s*/", + $render, ); } diff --git a/tests/StatsTest.php b/tests/StatsTest.php index c05516a7..cef00b08 100644 --- a/tests/StatsTest.php +++ b/tests/StatsTest.php @@ -93,6 +93,36 @@ public function testInvalidUsername(): void getContributionGraphs("help"); } + /** + * Test that an valid username can be accessed with whitelist + */ + public function testValidUsernameWithWhitelist(): void + { + $_SERVER["WHITELIST"] = "DenverCoder1"; + try { + $contributionGraphs = getContributionGraphs("DenverCoder1"); + $this->assertIsArray($contributionGraphs); + $this->assertNotEmpty($contributionGraphs); + } finally { + unset($_SERVER["WHITELIST"]); + } + } + + /** + * Test that an not whitelisted username returns 'not whitelisted' error + */ + public function testNotWhitelistedUsername(): void + { + $_SERVER["WHITELIST"] = "DenverCoder1"; + try { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage("User not in whitelist."); + getContributionGraphs("help"); + } finally { + unset($_SERVER["WHITELIST"]); + } + } + /** * Test that an organization name returns 'not a user' error */