Răsfoiți Sursa

Merge branch 'master' into crowdin-translation

El RIDO 2 ani în urmă
părinte
comite
1a4e2f8f06
55 a modificat fișierele cu 335 adăugiri și 158 ștergeri
  1. 50 0
      .devcontainer/devcontainer.json
  2. 3 0
      .gitattributes
  3. 45 17
      .github/DISCUSSION_TEMPLATE/q-a.yml
  4. 4 1
      .github/ISSUE_TEMPLATE/bug_report.md
  5. 0 3
      .github/ISSUE_TEMPLATE/config.yml
  6. 2 2
      .github/ISSUE_TEMPLATE/feature_request.md
  7. 1 1
      .github/workflows/codeql-analysis.yml
  8. 0 35
      .github/workflows/shiftleft-analysis.yml
  9. 1 1
      .github/workflows/snyk-scan.yml
  10. 3 3
      .github/workflows/tests.yml
  11. 7 0
      .vscode/extensions.json
  12. 35 0
      .vscode/launch.json
  13. 5 1
      CHANGELOG.md
  14. 2 2
      Makefile
  15. 1 1
      README.md
  16. 2 2
      SECURITY.md
  17. 1 1
      bin/administration
  18. 4 0
      cfg/conf.sample.php
  19. 26 25
      composer.lock
  20. 22 1
      css/bootstrap/privatebin.css
  21. 1 1
      css/noscript.css
  22. 16 1
      css/privatebin.css
  23. 10 9
      doc/Installation.md
  24. 0 1
      i18n/en.json
  25. 1 1
      index.php
  26. 1 1
      js/package.json
  27. 17 2
      js/privatebin.js
  28. 7 7
      js/test/I18n.js
  29. 2 1
      lib/Configuration.php
  30. 3 2
      lib/Controller.php
  31. 1 1
      lib/Data/AbstractData.php
  32. 1 1
      lib/Data/Database.php
  33. 1 1
      lib/Data/Filesystem.php
  34. 1 1
      lib/Data/S3Storage.php
  35. 1 1
      lib/Filter.php
  36. 1 1
      lib/FormatV2.php
  37. 13 1
      lib/I18n.php
  38. 1 1
      lib/Json.php
  39. 1 1
      lib/Model.php
  40. 1 1
      lib/Model/AbstractModel.php
  41. 1 1
      lib/Model/Comment.php
  42. 1 1
      lib/Model/Paste.php
  43. 1 1
      lib/Persistence/AbstractPersistence.php
  44. 1 1
      lib/Persistence/PurgeLimiter.php
  45. 1 1
      lib/Persistence/ServerSalt.php
  46. 1 1
      lib/Persistence/TrafficLimiter.php
  47. 1 1
      lib/Request.php
  48. 1 1
      lib/View.php
  49. 1 1
      lib/Vizhash16x16.php
  50. 1 1
      lib/YourlsProxy.php
  51. 11 2
      tpl/bootstrap.php
  52. 7 2
      tpl/page.php
  53. 1 1
      tpl/yourlsproxy.php
  54. 11 11
      tst/I18nTest.php
  55. 1 0
      tst/ViewTest.php

+ 50 - 0
.devcontainer/devcontainer.json

@@ -0,0 +1,50 @@
+{
+    "name": "PHP",
+    "image": "mcr.microsoft.com/devcontainers/php",
+    "customizations": {
+        "vscode": {
+            "extensions": [
+				"github.codespaces",
+                // PHP from https://github.com/devcontainers/templates/tree/main/src/php
+				"xdebug.php-debug",
+				"bmewburn.vscode-intelephense-client",
+				"xdebug.php-pack",
+                // PHP
+				"DEVSENSE.phptools-vscode",
+				"DEVSENSE.composer-php-vscode",
+                // linting
+				"EditorConfig.EditorConfig",
+				"dbaeumer.vscode-eslint",
+				"raymondcamden.CSSLint",
+                // testing
+				"maty.vscode-mocha-sidebar"
+			]
+        },
+        "codespaces": {
+            "openFiles": [
+                "README.md",
+                "doc/README.md"
+            ],
+            "repositories": {
+                "PrivateBin/*": {
+                    "permissions": {
+                        "pull_requests": "write"
+                    }
+                }
+            }
+        }
+    },
+    "features": {
+        "ghcr.io/devcontainers-contrib/features/mocha:2": {}
+    },
+    "forwardPorts": [
+        8080
+    ],
+    "postCreateCommand": [
+        "composer install --no-dev --optimize-autoloader",
+        "sudo chmod a+x \"$(pwd)\" && sudo rm -rf /var/www/html && sudo ln -s \"$(pwd)\" /var/www/html",
+        "npm install --global nyc"
+    ],
+    // alternatiuve: apache2ctl start (but requires root)
+    "postAttachCommand": "php -S 0.0.0.0:8080"
+}

+ 3 - 0
.gitattributes

@@ -2,6 +2,7 @@ bin/configuration-test-generator export-ignore
 bin/icon-test export-ignore
 doc/ export-ignore
 tst/ export-ignore
+i18n/en.json export-ignore
 img/browserstack.svg export-ignore
 js/.istanbul.yml export-ignore
 js/.nycrc.yml export-ignore
@@ -9,6 +10,7 @@ js/common.js export-ignore
 js/test/ export-ignore
 .codeclimate.yml export-ignore
 .csslintrc export-ignore
+.devcontainer export-ignore
 .editorconfig export-ignore
 .eslintignore export-ignore
 .eslintrc export-ignore
@@ -21,6 +23,7 @@ js/test/ export-ignore
 .scrutinizer.yml export-ignore
 .styleci.yml export-ignore
 .travis.yml export-ignore
+.vscode export-ignore
 codacy-analysis.yml export-ignore
 crowdin.yml export-ignore
 composer.json export-ignore

+ 45 - 17
.github/DISCUSSION_TEMPLATE/q-a.yml

@@ -1,62 +1,90 @@
-title: "[Question and support] "
 labels: ["question/support"]
 body:
   - type: markdown
     attributes:
       value: |
-        Describe the problem/question: A clear and concise description of what the problem/issue or question is.
+        ## Thanks for taking the time to fill out this form!
+  - type: textarea
+    id: description
+    attributes:
+      label: Describe the problem/question
+      description: A clear and concise description of what the problem/issue or question is.
+    validations:
+      required: true
   - type: checkboxes
+    id: readed-faq
     attributes:
-      label: Please tick this to confirm
+      label: Did you use the FAQ section?
+      description: Have you read [the FAQ](https://github.com/PrivateBin/PrivateBin/wiki/FAQ)?
       options:
         - label: Yes, I have read [the FAQ](https://github.com/PrivateBin/PrivateBin/wiki/FAQ) and I found no solution/answer there.
-  - type: markdown
+          required: true
+  - type: textarea
+    id: what_you_did
     attributes:
+      label: What you did?
+      description: Tell us how to reproduce the problem.
       value: |
-        ## What you did
-        Tell us how to reproduce the problem.
-
         1.
         2.
+        ...
+    validations:
+      required: true
   - type: textarea
     id: what_happens
     attributes:
       label: What happens
+      placeholder: Tell us what you see!
   - type: textarea
     id: what_should_happen
     attributes:
       label: What should happen
+      placeholder: Tell us what you want to see!
   - type: textarea
     id: additional_info
     attributes:
       label: Additional information
-      description: If you have access to the server log files, copy them here.
+      description: E.g. if you have access to the server log files, copy them here. Or copy the browser console content, if appropiate.
   - type: input
     id: server_address
     attributes:
       label: Server address
+      description: The instance of PrivateBin, where you experience the issue.
+      placeholder: e.g. https://privatebin.net
   - type: input
     id: server_os
     attributes:
       label: Server OS
+      placeholder: e.g. Ubuntu
   - type: input
     id: webserver
     attributes:
       label: Webserver
+      placeholder: e.g. Apache
+  - type: input
+    id: privatebin_version
+    attributes:
+      label: PrivateBin version
+      description: The PrivateBin version, where you experience the issue. It is e.g. shown at the bottom left in the web interface.
+      placeholder: e.g. v1.5.2
   - type: input
     id: browser
     attributes:
-      label: Browser
+      label: Browser and version
+      placeholder: e.g. Firefox v116.3.0 (desktop)
   - type: input
-    id: privatebin_version
+    id: client
     attributes:
-      label: PrivateBin version
-  - type: radio
+      label: Local operating system and version
+      placeholder: e.g. Windows 10
+  - type: dropdown
     id: reproduce_issue
     attributes:
-      label: I can reproduce this issue on [https://privatebin.net](https://privatebin.net)
+      label: Issue reproducibility
+      description: Can you reproduce this issue on [https://privatebin.net](https://privatebin.net)?
       options:
-        - label: Yes
-          value: Yes
-        - label: No
-          value: No
+        - "No, I cannot reproduce it on https://privatebin.net."
+        - "Yes, reproducible on https://privatebin.net."
+      default: 0
+    validations:
+      required: true

+ 4 - 1
.github/ISSUE_TEMPLATE/bug_report.md

@@ -10,7 +10,10 @@ assignees: ''
 <!-- Please make sure to **only** use this template when it is about bugs in the PrivateBin PHP project.
 Otherwise, for help and support issues e.g. for deployment issues, please go back and chose the appropiate category. -->
 
-<-- Describe the bug: A clear and concise description of what the bug is. -->
+**Did you use the FAQ section?**
+- [ ] Yes, I have read [the FAQ](https://github.com/PrivateBin/PrivateBin/wiki/FAQ) and I found no solution/answer there.
+
+<!-- Describe the bug: A clear and concise description of what the bug is. -->
 
 ## Steps to reproduce
 <!-- Tell us how to reproduce the problem. -->

+ 0 - 3
.github/ISSUE_TEMPLATE/config.yml

@@ -6,7 +6,4 @@ contact_links:
   - name: Problem with the container image
     url: https://github.com/PrivateBin/docker-nginx-fpm-alpine/issues/new
     about: Please report all problems that apply only(!) to the official (Docker) image “docker-nginx-fpm-alpine” here.
-  - name: Security issue
-    url: https://github.com/PrivateBin/PrivateBin/security/policy
-    about: Please report security vulnerabilities and other security issues here.
 

+ 2 - 2
.github/ISSUE_TEMPLATE/feature_request.md

@@ -9,11 +9,11 @@ assignees: ''
 
 ## The problem
 
-<-- Is your feature request related to a problem? Please describe.
+<!-- Is your feature request related to a problem? Please describe.
 A clear and concise description of what the problem is. Ex. I'm always frustrated when -->
 
 ## The solution
-<-- A clear and concise description of what you want to happen. Pitch your solution! What would happen, if we don't implement this? -->
+<!-- A clear and concise description of what you want to happen. Pitch your solution! What would happen, if we don't implement this? -->
 
 ## Alternatives
 <!-- A clear and concise description of any alternative solutions or features you've considered. -->

+ 1 - 1
.github/workflows/codeql-analysis.yml

@@ -33,7 +33,7 @@ jobs:
 
     steps:
     - name: Checkout repository
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
 
     # Initializes the CodeQL tools for scanning.
     - name: Initialize CodeQL

+ 0 - 35
.github/workflows/shiftleft-analysis.yml

@@ -1,35 +0,0 @@
-# This workflow integrates Scan with GitHub's code scanning feature
-# Scan is a free open-source security tool for modern DevOps teams from ShiftLeft
-# Visit https://slscan.io/en/latest/integrations/code-scan for help
-name: SL Scan
-
-on:
-  push:
-    branches: [ master ]
-  pull_request:
-    # The branches below must be a subset of the branches above
-    branches: [ master ]
-  schedule:
-    - cron: '16 22 * * 4'
-
-jobs:
-  Scan-Build:
-    # Scan runs on ubuntu, mac and windows
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v3
-    # potentially add composer install steo here
-    - name: Perform Scan
-      uses: ShiftLeftSecurity/scan-action@master
-      env:
-        WORKSPACE: ""
-        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-        SCAN_AUTO_BUILD: true
-      with:
-        output: reports
-        # Scan auto-detects the languages.
-
-    - name: Upload report
-      uses: github/codeql-action/upload-sarif@v2
-      with:
-        sarif_file: reports

+ 1 - 1
.github/workflows/snyk-scan.yml

@@ -13,7 +13,7 @@ jobs:
   snyk-php:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@v3
+      - uses: actions/checkout@v4
       - name: Install Google Cloud Storage
         run: composer require --no-update google/cloud-storage && composer update --no-dev
       - name: Run Snyk to check for vulnerabilities

+ 3 - 3
.github/workflows/tests.yml

@@ -9,7 +9,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - name: Checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
     - name: Validate composer.json and composer.lock
       run: composer validate
     - name: Install dependencies
@@ -29,7 +29,7 @@ jobs:
 
     # let's get started!
     - name: Checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
 
     # cache PHP extensions
     - name: Setup cache environment
@@ -99,7 +99,7 @@ jobs:
     steps:
 
     - name: Checkout
-      uses: actions/checkout@v3
+      uses: actions/checkout@v4
 
     - name: Setup Node
       uses: actions/setup-node@v3

+ 7 - 0
.vscode/extensions.json

@@ -0,0 +1,7 @@
+{
+    "recommendations": [
+        "recca0120.vscode-phpunit",
+        "onecentlin.phpunit-snippets",
+        "devsense.profiler-php-vscode"
+    ]
+}

+ 35 - 0
.vscode/launch.json

@@ -0,0 +1,35 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Launch built-in server and debug",
+            "type": "php",
+            "request": "launch",
+            "runtimeArgs": [
+                "-S",
+                "localhost:8000",
+                "-t",
+                "."
+            ],
+            "port": 9003,
+            "serverReadyAction": {
+                "action": "openExternally"
+            }
+        },
+        {
+            "name": "Debug current script in console",
+            "type": "php",
+            "request": "launch",
+            "program": "${file}",
+            "cwd": "${fileDirname}",
+            "externalConsole": false,
+            "port": 9003
+        },
+        {
+            "name": "Listen for Xdebug",
+            "type": "php",
+            "request": "launch",
+            "port": 9003
+        }
+    ]
+}

+ 5 - 1
CHANGELOG.md

@@ -1,8 +1,12 @@
 # PrivateBin version history
 
-  * **1.6.0 (not yet released)**
+  * **1.6.1 (not yet released)**
+    * ADDED: Right-To-Left (RTL) support for Arabic & Hebrew (#1174)
+  * **1.6.0 (2023-09-11)**
     * ADDED: Translations for Japanese & Arabic
+    * ADDED: Configuration option to disable Email button (#1164)
     * CHANGED: Minimum required PHP version is 7.3, due to upgrading PHPunit (#707)
+    * CHANGED: Removed PHP 5 polyfill for random_bytes()
   * **1.5.2 (2023-07-09)**
     * ADDED: Allow AWS SDK to use default credential provider chain for S3Storage (#1070)
     * CHANGED: Upgrading libraries to: DOMpurify 3.0.4 & jQuery 3.7.0

+ 2 - 2
Makefile

@@ -1,7 +1,7 @@
 .PHONY: all coverage coverage-js coverage-php doc doc-js doc-php increment sign test test-js test-php help
 
-CURRENT_VERSION = 1.5.2
-VERSION ?= 1.5.3
+CURRENT_VERSION = 1.6.0
+VERSION ?= 1.6.1
 VERSION_FILES = index.php bin/ cfg/ *.md doc/Installation.md css/ i18n/ img/ js/package.json js/privatebin.js lib/ Makefile tpl/ tst/
 REGEX_CURRENT_VERSION := $(shell echo $(CURRENT_VERSION) | sed "s/\./\\\./g")
 REGEX_VERSION := $(shell echo $(VERSION) | sed "s/\./\\\./g")

+ 1 - 1
README.md

@@ -1,6 +1,6 @@
 # [![PrivateBin](https://cdn.rawgit.com/PrivateBin/assets/master/images/preview/logoSmall.png)](https://privatebin.info/)
 
-*Current version: 1.5.2*
+*Current version: 1.6.0*
 
 **PrivateBin** is a minimalist, open source online
 [pastebin](https://en.wikipedia.org/wiki/Pastebin)

+ 2 - 2
SECURITY.md

@@ -4,8 +4,8 @@
 
 | Version | Supported          |
 | ------- | ------------------ |
-| 1.5.2   | :heavy_check_mark: |
-| < 1.5.2 | :x:                |
+| 1.6.0   | :heavy_check_mark: |
+| < 1.6.0 | :x:                |
 
 ## Reporting a Vulnerability
 

+ 1 - 1
bin/administration

@@ -9,7 +9,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;

+ 4 - 0
cfg/conf.sample.php

@@ -65,6 +65,10 @@ languageselection = false
 ; It works both when a new paste is created and when you view a paste.
 ; qrcode = true
 
+; (optional) Let users send an email sharing the paste URL with one click.
+; It works both when a new paste is created and when you view a paste.
+; email = true
+
 ; (optional) IP based icons are a weak mechanism to detect if a comment was from
 ; a different user when the same username was used in a comment. It might get
 ; used to get the IP of a comment poster if the server salt is leaked and a

+ 26 - 25
composer.lock

@@ -316,16 +316,16 @@
         },
         {
             "name": "nikic/php-parser",
-            "version": "v4.16.0",
+            "version": "v4.17.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/nikic/PHP-Parser.git",
-                "reference": "19526a33fb561ef417e822e85f08a00db4059c17"
+                "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
-                "reference": "19526a33fb561ef417e822e85f08a00db4059c17",
+                "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
+                "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
                 "shasum": ""
             },
             "require": {
@@ -366,9 +366,9 @@
             ],
             "support": {
                 "issues": "https://github.com/nikic/PHP-Parser/issues",
-                "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
+                "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
             },
-            "time": "2023-06-25T14:52:30+00:00"
+            "time": "2023-08-13T19:53:39+00:00"
         },
         {
             "name": "phar-io/manifest",
@@ -483,16 +483,16 @@
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "9.2.26",
+            "version": "9.2.29",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
+                "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
-                "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76",
+                "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76",
                 "shasum": ""
             },
             "require": {
@@ -548,7 +548,8 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
-                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
+                "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+                "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29"
             },
             "funding": [
                 {
@@ -556,7 +557,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2023-03-06T12:58:08+00:00"
+            "time": "2023-09-19T04:57:46+00:00"
         },
         {
             "name": "phpunit/php-file-iterator",
@@ -801,16 +802,16 @@
         },
         {
             "name": "phpunit/phpunit",
-            "version": "9.6.10",
+            "version": "9.6.13",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328"
+                "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328",
-                "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be",
+                "reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be",
                 "shasum": ""
             },
             "require": {
@@ -825,7 +826,7 @@
                 "phar-io/manifest": "^2.0.3",
                 "phar-io/version": "^3.0.2",
                 "php": ">=7.3",
-                "phpunit/php-code-coverage": "^9.2.13",
+                "phpunit/php-code-coverage": "^9.2.28",
                 "phpunit/php-file-iterator": "^3.0.5",
                 "phpunit/php-invoker": "^3.1.1",
                 "phpunit/php-text-template": "^2.0.3",
@@ -884,7 +885,7 @@
             "support": {
                 "issues": "https://github.com/sebastianbergmann/phpunit/issues",
                 "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
-                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10"
+                "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13"
             },
             "funding": [
                 {
@@ -900,7 +901,7 @@
                     "type": "tidelift"
                 }
             ],
-            "time": "2023-07-10T04:04:23+00:00"
+            "time": "2023-09-19T05:39:22+00:00"
         },
         {
             "name": "sebastian/cli-parser",
@@ -1408,16 +1409,16 @@
         },
         {
             "name": "sebastian/global-state",
-            "version": "5.0.5",
+            "version": "5.0.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/global-state.git",
-                "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
+                "reference": "bde739e7565280bda77be70044ac1047bc007e34"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
-                "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+                "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34",
+                "reference": "bde739e7565280bda77be70044ac1047bc007e34",
                 "shasum": ""
             },
             "require": {
@@ -1460,7 +1461,7 @@
             ],
             "support": {
                 "issues": "https://github.com/sebastianbergmann/global-state/issues",
-                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
+                "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6"
             },
             "funding": [
                 {
@@ -1468,7 +1469,7 @@
                     "type": "github"
                 }
             ],
-            "time": "2022-02-14T08:28:10+00:00"
+            "time": "2023-08-02T09:26:13+00:00"
         },
         {
             "name": "sebastian/lines-of-code",

+ 22 - 1
css/bootstrap/privatebin.css

@@ -6,7 +6,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 body {
@@ -113,6 +113,7 @@ body.loading {
 #qrcodemodalClose {
 	float: right;
 }
+
 #qrcode-display {
 	width: 200px;
 	height: 200px;
@@ -205,3 +206,23 @@ li.L0, li.L1, li.L2, li.L3, li.L5, li.L6, li.L7, li.L8 {
 .modal .modal-content button {
 	margin: 0.5em 0;
 }
+
+/* right-to-left overrides */
+html[dir="rtl"] .checkbox label {
+	padding-left: inherit;
+	padding-right: 20px;
+}
+
+html[dir="rtl"] .checkbox input[type="checkbox"] {
+	margin-left: inherit;
+	margin-right: -20px;
+}
+
+html[dir="rtl"] #language {
+	margin-left: inherit;
+	margin-right: 8px;
+}
+
+html[dir="rtl"] #deletelink, html[dir="rtl"] #qrcodemodalClose {
+	float: left;
+}

+ 1 - 1
css/noscript.css

@@ -6,7 +6,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 /* When there is no script at all other */

+ 16 - 1
css/privatebin.css

@@ -6,7 +6,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 /*  CSS Reset from YUI 3.4.1 (build 4118) - Copyright 2011 Yahoo! Inc. All rights reserved.
@@ -261,6 +261,7 @@ button img {
 
 #newbutton {
 	float: right;
+	margin-left: 0;
 	margin-right: 0;
 	margin-bottom: 5px;
 	display: inline;
@@ -488,3 +489,17 @@ img.vizhash {
 #cleartext h3 {
 	font-size: 1.2em;
 }
+
+/* right-to-left overrides */
+html[dir="rtl"] #aboutbox, html[dir="rtl"] #deletelink, html[dir="rtl"] #newbutton {
+	float: left;
+}
+
+html[dir="rtl"] button, html[dir="rtl"] .button, html[dir="rtl"] button img {
+	margin-left: 5px;
+	margin-right: inherit;
+}
+
+html[dir="rtl"] button img {
+	margin-left: 8px;
+}

+ 10 - 9
doc/Installation.md

@@ -13,10 +13,11 @@ mostly safe default configuration, but we urge you to check the
 **NOTE:** See our [FAQ entry on securely downloading release files](https://github.com/PrivateBin/PrivateBin/wiki/FAQ#how-can-i-securely-clonedownload-your-project)
 for more information.
 
-**NOTE:** There is a [ansible](https://ansible.com) role by @e1mo available to
-install and configure PrivateBin on your server. It's available on
-[ansible galaxy](https://galaxy.ansible.com/e1mo/privatebin)
-([source code](https://git.sr.ht/~e1mo/ansible-role-privatebin)).
+**NOTE:** There are Ansible roles available for installing and configuring PrivateBin on your server. You can choose from the following options:
+
+- [Podman Rootless - PrivateBin by @voidquark](https://galaxy.ansible.com/voidquark/privatebin)  ([Github source code](https://github.com/voidquark/privatebin)): Simplifies the deployment and management of a secure PrivateBin service using a rootless Podman container. Key features include root-less deployment, ensuring security within a user namespace, idempotent deployment for consistent state, out-of-the-box setup for Red Hat systems, and the flexibility to customize PrivateBin configurations. It has been tested on EL9.
+
+- [Config Configuration - PrivateBin by @e1mo](https://galaxy.ansible.com/e1mo/privatebin) ([Github source code](https://git.sr.ht/~e1mo/ansible-role-privatebin)): Deploy PrivateBin configuration to disk with a customized configuration.
 
 ### Minimal Requirements
 
@@ -58,7 +59,7 @@ accessible to your webserver and PHP process (see also
 In situations where you want to keep the PrivateBin static files separate from the
 rest of your data, or you want to reuse the installation files on multiple vhosts,
 you may only want to change the `conf.php`. In this case, you can set the
-`CONFIG_PATH` environment variable to the absolute path to the `conf.php` file.
+`CONFIG_PATH` environment variable to the absolute path to the directory containing the `conf.php` file.
 This can be done in your web server's virtual host config, the PHP config, or in
 the index.php, if you choose to customize it.
 
@@ -67,13 +68,13 @@ wherever it may be.
 
 > #### CONFIG_PATH example
 > Setting the value in an Apache Vhost:
-> SetEnv CONFIG_PATH /var/lib/privatebin/conf.php
+> SetEnv CONFIG_PATH /var/lib/privatebin/
 >
 > In a php-fpm pool config:
-> env[CONFIG_PATH] = /var/lib/privatebin/conf.php
+> env[CONFIG_PATH] = /var/lib/privatebin/
 >
 > In the index.php, near the top:
-> putenv('CONFIG_PATH=/var/lib/privatebin/conf.php');
+> putenv('CONFIG_PATH=/var/lib/privatebin/');
 
 ### Transport security
 
@@ -200,7 +201,7 @@ CREATE INDEX parent ON prefix_comment(pasteid);
 CREATE TABLE prefix_config (
     id CHAR(16) NOT NULL, value TEXT, PRIMARY KEY (id)
 );
-INSERT INTO prefix_config VALUES('VERSION', '1.5.2');
+INSERT INTO prefix_config VALUES('VERSION', '1.6.0');
 ```
 
 In **PostgreSQL**, the `data`, `attachment`, `nickname` and `vizhash` columns

+ 0 - 1
i18n/en.json

@@ -3,7 +3,6 @@
     "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.": "%s is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted %sin the browser%s using 256 bits AES.",
     "More information on the <a href=\"https://privatebin.info/\">project page</a>.": "More information on the <a href=\"https://privatebin.info/\">project page</a>.",
     "Because ignorance is bliss": "Because ignorance is bliss",
-    "en": "en",
     "Paste does not exist, has expired or has been deleted.": "Paste does not exist, has expired or has been deleted.",
     "%s requires php %s or above to work. Sorry.": "%s requires php %s or above to work. Sorry.",
     "%s requires configuration section [%s] to be present in configuration file.": "%s requires configuration section [%s] to be present in configuration file.",

+ 1 - 1
index.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 // change this, if your php files and data is outside of your webservers document root

+ 1 - 1
js/package.json

@@ -1,6 +1,6 @@
 {
   "name": "privatebin",
-  "version": "1.5.2",
+  "version": "1.6.0",
   "description": "PrivateBin is a minimalist, open source online pastebin where the server has zero knowledge of pasted data. Data is encrypted/decrypted in the browser using 256 bit AES in Galois Counter mode (GCM).",
   "main": "privatebin.js",
   "directories": {

+ 17 - 2
js/privatebin.js

@@ -6,7 +6,7 @@
  * @see       {@link https://github.com/PrivateBin/PrivateBin}
  * @copyright 2012 Sébastien SAUVAGE ({@link http://sebsauvage.net})
  * @license   {@link https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License}
- * @version   1.5.2
+ * @version   1.6.0
  * @name      PrivateBin
  * @namespace
  */
@@ -790,6 +790,18 @@ jQuery.PrivateBin = (function($, RawDeflate) {
             return output;
         };
 
+        /**
+         * get currently loaded language
+         *
+         * @name   I18n.getLanguage
+         * @function
+         * @return {string}
+         */
+        me.getLanguage = function()
+        {
+            return language;
+        };
+
         /**
          * per language functions to use to determine the plural form
          *
@@ -847,7 +859,10 @@ jQuery.PrivateBin = (function($, RawDeflate) {
 
             // auto-select language based on browser settings
             if (newLanguage.length === 0) {
-                newLanguage = (navigator.language || navigator.userLanguage || 'en').substring(0, 2);
+                newLanguage = (navigator.language || navigator.userLanguage || 'en');
+                if (newLanguage.indexOf('-') > 0) {
+                    newLanguage = newLanguage.split('-')[0];
+                }
             }
 
             // if language is already used skip update

+ 7 - 7
js/test/I18n.js

@@ -193,10 +193,11 @@ describe('I18n', function () {
                 // mock
                 clean = jsdom('', {cookie: ['lang=' + language]});
                 $.PrivateBin.I18n.reset(language, require('../../i18n/' + language + '.json'));
-                var result = $.PrivateBin.I18n.translate('en'),
-                    alias  = $.PrivateBin.I18n._('en');
+                var loadedLang = $.PrivateBin.I18n.getLanguage(),
+                    result = $.PrivateBin.I18n.translate('Never'),
+                    alias  = $.PrivateBin.I18n._('Never');
                 clean();
-                return language === result && language === alias;
+                return language === loadedLang && result === alias;
             }
         );
 
@@ -216,13 +217,12 @@ describe('I18n', function () {
 
                 $.PrivateBin.I18n.reset('en');
                 $.PrivateBin.I18n.loadTranslations();
-                var result = $.PrivateBin.I18n.translate('en'),
-                    alias  = $.PrivateBin.I18n._('en');
+                var result = $.PrivateBin.I18n.translate('Never'),
+                    alias  = $.PrivateBin.I18n._('Never');
 
                 clean();
-                return 'en' === result && 'en' === alias;
+                return 'Never' === result && 'Never' === alias;
             }
         );
     });
 });
-

+ 2 - 1
lib/Configuration.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;
@@ -53,6 +53,7 @@ class Configuration
             'languagedefault'          => '',
             'urlshortener'             => '',
             'qrcode'                   => true,
+            'email'                    => true,
             'icon'                     => 'identicon',
             'cspheader'                => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
             'zerobincompatibility'     => false,

+ 3 - 2
lib/Controller.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;
@@ -28,7 +28,7 @@ class Controller
      *
      * @const string
      */
-    const VERSION = '1.5.2';
+    const VERSION = '1.6.0';
 
     /**
      * minimal required PHP version
@@ -411,6 +411,7 @@ class Controller
         $page->assign('EXPIREDEFAULT', $this->_conf->getKey('default', 'expire'));
         $page->assign('URLSHORTENER', $this->_conf->getKey('urlshortener'));
         $page->assign('QRCODE', $this->_conf->getKey('qrcode'));
+        $page->assign('EMAIL', $this->_conf->getKey('email'));
         $page->assign('HTTPWARNING', $this->_conf->getKey('httpwarning'));
         $page->assign('HTTPSLINK', 'https://' . $this->_request->getHost() . $this->_request->getRequestUri());
         $page->assign('COMPRESSION', $this->_conf->getKey('compression'));

+ 1 - 1
lib/Data/AbstractData.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Data;

+ 1 - 1
lib/Data/Database.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Data;

+ 1 - 1
lib/Data/Filesystem.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Data;

+ 1 - 1
lib/Data/S3Storage.php

@@ -4,7 +4,7 @@
  *
  * an S3 compatible data backend for PrivateBin with CEPH/RadosGW in mind
  * see https://docs.ceph.com/en/latest/radosgw/s3/php/
- * based on lib/Data/GoogleCloudStorage.php from PrivateBin version 1.5.2
+ * based on lib/Data/GoogleCloudStorage.php from PrivateBin version 1.6.0
  *
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2022 Felix J. Ogris (https://ogris.de/)

+ 1 - 1
lib/Filter.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;

+ 1 - 1
lib/FormatV2.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;

+ 13 - 1
lib/I18n.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;
@@ -272,6 +272,18 @@ class I18n
         return array_intersect_key(self::$_languageLabels, array_flip($languages));
     }
 
+    /**
+     * determines if the current language is written right-to-left (RTL)
+     *
+     * @access public
+     * @static
+     * @return bool
+     */
+    public static function isRtl()
+    {
+        return in_array(self::$_language, array('ar', 'he'));
+    }
+
     /**
      * set the default language
      *

+ 1 - 1
lib/Json.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;

+ 1 - 1
lib/Model.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;

+ 1 - 1
lib/Model/AbstractModel.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Model;

+ 1 - 1
lib/Model/Comment.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Model;

+ 1 - 1
lib/Model/Paste.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Model;

+ 1 - 1
lib/Persistence/AbstractPersistence.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Persistence;

+ 1 - 1
lib/Persistence/PurgeLimiter.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Persistence;

+ 1 - 1
lib/Persistence/ServerSalt.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Persistence;

+ 1 - 1
lib/Persistence/TrafficLimiter.php

@@ -8,7 +8,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin\Persistence;

+ 1 - 1
lib/Request.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;

+ 1 - 1
lib/View.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;

+ 1 - 1
lib/Vizhash16x16.php

@@ -8,7 +8,7 @@
  * @link      https://sebsauvage.net/wiki/doku.php?id=php:vizhash_gd
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   0.0.5 beta PrivateBin 1.5.2
+ * @version   0.0.5 beta PrivateBin 1.6.0
  */
 
 namespace PrivateBin;

+ 1 - 1
lib/YourlsProxy.php

@@ -7,7 +7,7 @@
  * @link      https://github.com/PrivateBin/PrivateBin
  * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
  * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
- * @version   1.5.2
+ * @version   1.6.0
  */
 
 namespace PrivateBin;

+ 11 - 2
tpl/bootstrap.php

@@ -4,7 +4,7 @@ $isCpct = substr($template, 9, 8) === '-compact';
 $isDark = substr($template, 9, 5) === '-dark';
 $isPage = substr($template, -5) === '-page';
 ?><!DOCTYPE html>
-<html lang="<?php echo I18n::_('en'); ?>">
+<html lang="<?php echo I18n::getLanguage(); ?>"<?php echo I18n::isRtl() ? ' dir="rtl"' : ''; ?>>
 	<head>
 		<meta charset="utf-8" />
 		<meta http-equiv="Content-Security-Policy" content="<?php echo I18n::encode($CSPHEADER); ?>">
@@ -73,7 +73,7 @@ endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-3.0.4.js" integrity="sha512-N7H+3ylaOUeKuTX57cZoa42hqaG5w1rchG/IP9+BHd48W/vESgPDpb5QuDqzJE1dZhrGVCQgU8peIQGHmdGFhQ==" crossorigin="anonymous"></script>
 		<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-PDohiS3326HlXcagz8Q2xejgrixZP/LD9/fTbOygHiunBSyCxhqakMOYQ8NrS5J8EozG7GRaZ1yUUr5URH1WZQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-ykPMZuZDmyGNik7G5dIeqE/+CJ79OKZ0XzPPVWUwAzp+k8PQoP66J8F8zYtI53dM4ITLojkNKrv4vTv6E3bzFQ==" crossorigin="anonymous"></script>
 		<!-- icon -->
 		<link rel="apple-touch-icon" href="<?php echo I18n::encode($BASEPATH); ?>img/apple-touch-icon.png" sizes="180x180" />
 		<link rel="icon" type="image/png" href="img/favicon-32x32.png" sizes="32x32" />
@@ -144,6 +144,7 @@ if ($QRCODE) :
 		</div>
 <?php
 endif;
+if ($EMAIL) :
 ?>
 		<div id="emailconfirmmodal" tabindex="-1" class="modal fade" aria-labelledby="emailconfirmmodalTitle" role="dialog" aria-hidden="true">
 			<div class="modal-dialog" role="document">
@@ -167,6 +168,9 @@ endif;
 				</div>
 			</div>
 		</div>
+<?php
+endif;
+?>
 		<nav class="navbar navbar-<?php echo $isDark ? 'inverse' : 'default'; ?> navbar-<?php echo $isCpct ? 'fixed' : 'static'; ?>-top"><?php
 if ($isCpct) :
 ?><div class="container"><?php
@@ -216,10 +220,15 @@ endif;
 						<button id="downloadtextbutton" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
 							<span class="glyphicon glyphicon glyphicon-download-alt" aria-hidden="true"></span> <?php echo I18n::_('Save paste'), PHP_EOL; ?>
 						</button>
+<?php
+if ($EMAIL) :
+?>
+
 						<button id="emaillink" type="button" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">
 							<span class="glyphicon glyphicon-envelope" aria-hidden="true"></span> <?php echo I18n::_('Email'), PHP_EOL; ?>
 						</button>
 <?php
+endif;
 if ($QRCODE) :
 ?>
 						<button id="qrcodelink" type="button" data-toggle="modal" data-target="#qrcodemodal" class="hidden btn btn-<?php echo $isDark ? 'warning' : 'default'; ?> navbar-btn">

+ 7 - 2
tpl/page.php

@@ -1,7 +1,7 @@
 <?php
 use PrivateBin\I18n;
 ?><!DOCTYPE html>
-<html lang="<?php echo I18n::_('en'); ?>">
+<html lang="<?php echo I18n::getLanguage(); ?>"<?php echo I18n::isRtl() ? ' dir="rtl"' : ''; ?>>
 	<head>
 		<meta charset="utf-8" />
 		<meta http-equiv="Content-Security-Policy" content="<?php echo I18n::encode($CSPHEADER); ?>">
@@ -51,7 +51,7 @@ endif;
 ?>
 		<script type="text/javascript" data-cfasync="false" src="js/purify-3.0.4.js" integrity="sha512-N7H+3ylaOUeKuTX57cZoa42hqaG5w1rchG/IP9+BHd48W/vESgPDpb5QuDqzJE1dZhrGVCQgU8peIQGHmdGFhQ==" crossorigin="anonymous"></script>
 		<script type="text/javascript" data-cfasync="false" src="js/legacy.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-LYos+qXHIRqFf5ZPNphvtTB0cgzHUizu2wwcOwcwz/VIpRv9lpcBgPYz4uq6jx0INwCAj6Fbnl5HoKiLufS2jg==" crossorigin="anonymous"></script>
-		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-PDohiS3326HlXcagz8Q2xejgrixZP/LD9/fTbOygHiunBSyCxhqakMOYQ8NrS5J8EozG7GRaZ1yUUr5URH1WZQ==" crossorigin="anonymous"></script>
+		<script type="text/javascript" data-cfasync="false" src="js/privatebin.js?<?php echo rawurlencode($VERSION); ?>" integrity="sha512-ykPMZuZDmyGNik7G5dIeqE/+CJ79OKZ0XzPPVWUwAzp+k8PQoP66J8F8zYtI53dM4ITLojkNKrv4vTv6E3bzFQ==" crossorigin="anonymous"></script>
 		<!-- icon -->
 		<link rel="apple-touch-icon" href="img/apple-touch-icon.png?<?php echo rawurlencode($VERSION); ?>" sizes="180x180" />
 		<link rel="icon" type="image/png" href="img/favicon-32x32.png?<?php echo rawurlencode($VERSION); ?>" sizes="32x32" />
@@ -129,8 +129,13 @@ endif;
 					<button id="clonebutton" class="hidden"><img src="img/icon_clone.png" width="15" height="17" alt="" /><?php echo I18n::_('Clone'); ?></button>
 					<button id="rawtextbutton" class="hidden"><img src="img/icon_raw.png" width="15" height="15" alt="" /><?php echo I18n::_('Raw text'); ?></button>
 					<button id="downloadtextbutton" class="hidden"><?php echo I18n::_('Save paste'), PHP_EOL; ?></button>
+<?php
+if ($EMAIL):
+?>
+
 					<button id="emaillink" class="hidden"><img src="img/icon_email.png" width="15" height="15" alt="" /><?php echo I18n::_('Email'); ?></button>
 <?php
+endif;
 if ($QRCODE):
 ?>
 					<button id="qrcodelink" class="hidden"><img src="img/icon_qr.png" width="15" height="15" alt="" /><?php echo I18n::_('QR code'); ?></button>

+ 1 - 1
tpl/yourlsproxy.php

@@ -1,7 +1,7 @@
 <?php
 use PrivateBin\I18n;
 ?><!DOCTYPE html>
-<html lang="<?php echo I18n::_('en'); ?>">
+<html lang="<?php echo I18n::getLanguage(); ?>"<?php echo I18n::isRtl() ? ' dir="rtl"' : ''; ?>>
 	<head>
 		<meta charset="utf-8" />
 		<meta http-equiv="Content-Security-Policy" content="<?php echo I18n::encode($CSPHEADER); ?>">

+ 11 - 11
tst/I18nTest.php

@@ -34,7 +34,7 @@ class I18nTest extends TestCase
     {
         $_COOKIE['lang'] = 'de';
         I18n::loadTranslations();
-        $this->assertEquals($this->_translations['en'], I18n::_('en'), 'browser language de');
+        $this->assertEquals($_COOKIE['lang'], I18n::getLanguage(), 'browser language de');
         $this->assertEquals('0 Stunden', I18n::_('%d hours', 0), '0 hours in German');
         $this->assertEquals('1 Stunde',  I18n::_('%d hours', 1), '1 hour in German');
         $this->assertEquals('2 Stunden', I18n::_('%d hours', 2), '2 hours in German');
@@ -44,7 +44,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'de-CH,de;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2,fr;q=0.0';
         I18n::loadTranslations();
-        $this->assertEquals($this->_translations['en'], I18n::_('en'), 'browser language de');
+        $this->assertEquals('de', I18n::getLanguage(), 'browser language de');
         $this->assertEquals('0 Stunden', I18n::_('%d hours', 0), '0 hours in German');
         $this->assertEquals('1 Stunde',  I18n::_('%d hours', 1), '1 hour in German');
         $this->assertEquals('2 Stunden', I18n::_('%d hours', 2), '2 hours in German');
@@ -54,7 +54,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'fr-CH,fr;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2,de;q=0.0';
         I18n::loadTranslations();
-        $this->assertEquals('fr', I18n::_('en'), 'browser language fr');
+        $this->assertEquals('fr', I18n::getLanguage(), 'browser language fr');
         $this->assertEquals('0 heure',  I18n::_('%d hours', 0), '0 hours in French');
         $this->assertEquals('1 heure',  I18n::_('%d hours', 1), '1 hour in French');
         $this->assertEquals('2 heures', I18n::_('%d hours', 2), '2 hours in French');
@@ -64,7 +64,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'no;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         I18n::loadTranslations();
-        $this->assertEquals('no', I18n::_('en'), 'browser language no');
+        $this->assertEquals('no', I18n::getLanguage(), 'browser language no');
         $this->assertEquals('0 timer',  I18n::_('%d hours', 0), '0 hours in Norwegian');
         $this->assertEquals('1 time',  I18n::_('%d hours', 1), '1 hour in Norwegian');
         $this->assertEquals('2 timer', I18n::_('%d hours', 2), '2 hours in Norwegian');
@@ -74,7 +74,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'oc;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         I18n::loadTranslations();
-        $this->assertEquals('oc', I18n::_('en'), 'browser language oc');
+        $this->assertEquals('oc', I18n::getLanguage(), 'browser language oc');
         $this->assertEquals('0 ora',  I18n::_('%d hours', 0), '0 hours in Occitan');
         $this->assertEquals('1 ora',  I18n::_('%d hours', 1), '1 hour in Occitan');
         $this->assertEquals('2 oras', I18n::_('%d hours', 2), '2 hours in Occitan');
@@ -84,7 +84,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'zh;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         I18n::loadTranslations();
-        $this->assertEquals('zh', I18n::_('en'), 'browser language zh');
+        $this->assertEquals('zh', I18n::getLanguage(), 'browser language zh');
         $this->assertEquals('0 小时',  I18n::_('%d hours', 0), '0 hours in Chinese');
         $this->assertEquals('1 小时',  I18n::_('%d hours', 1), '1 hour in Chinese');
         $this->assertEquals('2 小时', I18n::_('%d hours', 2), '2 hours in Chinese');
@@ -94,7 +94,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'pl;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         I18n::loadTranslations();
-        $this->assertEquals('pl', I18n::_('en'), 'browser language pl');
+        $this->assertEquals('pl', I18n::getLanguage(), 'browser language pl');
         $this->assertEquals('1 godzina', I18n::_('%d hours', 1), '1 hour in Polish');
         $this->assertEquals('2 godzina', I18n::_('%d hours', 2), '2 hours in Polish');
         $this->assertEquals('12 godzinę', I18n::_('%d hours', 12), '12 hours in Polish');
@@ -109,7 +109,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'ru;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         I18n::loadTranslations();
-        $this->assertEquals('ru', I18n::_('en'), 'browser language ru');
+        $this->assertEquals('ru', I18n::getLanguage(), 'browser language ru');
         $this->assertEquals('1 минуту',  I18n::_('%d minutes', 1), '1 minute in Russian');
         $this->assertEquals('3 минуты',  I18n::_('%d minutes', 3), '3 minutes in Russian');
         $this->assertEquals('10 минут',  I18n::_('%d minutes', 10), '10 minutes in Russian');
@@ -120,7 +120,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'sl;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         I18n::loadTranslations();
-        $this->assertEquals('sl', I18n::_('en'), 'browser language sl');
+        $this->assertEquals('sl', I18n::getLanguage(), 'browser language sl');
         $this->assertEquals('0 ura',  I18n::_('%d hours', 0), '0 hours in Slowene');
         $this->assertEquals('1 uri',  I18n::_('%d hours', 1), '1 hour in Slowene');
         $this->assertEquals('2 ure', I18n::_('%d hours', 2), '2 hours in Slowene');
@@ -135,7 +135,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'cs;q=0.8,en-GB;q=0.6,en-US;q=0.4,en;q=0.2';
         I18n::loadTranslations();
-        $this->assertEquals('cs', I18n::_('en'), 'browser language cs');
+        $this->assertEquals('cs', I18n::getLanguage(), 'browser language cs');
         $this->assertEquals('1 hodina', I18n::_('%d hours', 1), '1 hour in Czech');
         $this->assertEquals('2 hodiny', I18n::_('%d hours', 2), '2 hours in Czech');
         $this->assertEquals('5 minut',  I18n::_('%d minutes', 5), '5 minutes in Czech');
@@ -146,7 +146,7 @@ class I18nTest extends TestCase
     {
         $_SERVER['HTTP_ACCEPT_LANGUAGE'] = '*';
         I18n::loadTranslations();
-        $this->assertTrue(strlen(I18n::_('en')) >= 2, 'browser language any');
+        $this->assertTrue(strlen(I18n::getLanguage()) >= 2, 'browser language any');
     }
 
     public function testVariableInjection()

+ 1 - 0
tst/ViewTest.php

@@ -58,6 +58,7 @@ class ViewTest extends TestCase
         $page->assign('EXPIREDEFAULT', self::$expire_default);
         $page->assign('URLSHORTENER', '');
         $page->assign('QRCODE', true);
+        $page->assign('EMAIL', true);
         $page->assign('HTTPWARNING', true);
         $page->assign('HTTPSLINK', 'https://example.com/');
         $page->assign('COMPRESSION', 'zlib');