diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2dc2e3c7..10fab74d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,9 @@
# PrivateBin version history
* **next (not yet released)**
- * ADDED: Translations for Spanish, Occitan and Norwegian
+ * ADDED: Translations for Spanish, Occitan, Norwegian and Portuguese
* ADDED: Option in configuration to change the default "PrivateBin" title of the site
+ * CHANGED: Minimum required PHP version is 5.4 (#186)
* CHANGED: Cleanup of bootstrap template variants and moved icons to `img` directory
* **1.1 (2016-12-26)**
* ADDED: Translations for Italian and Russian
diff --git a/CREDITS.md b/CREDITS.md
index dfb2d83e..1c7ec3cc 100644
--- a/CREDITS.md
+++ b/CREDITS.md
@@ -35,3 +35,4 @@ Sébastien Sauvage - original idea and main developer
* Alfredo Fabián Altamirano Tena - Spanish
* Quent-in - Occitan
* idarlund - Norwegian
+* Tulio Leao - Portuguese
diff --git a/i18n/es.json b/i18n/es.json
index 0af97ebf..1e2fd48f 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -10,7 +10,7 @@
"%s requires php 5.3.0 or above to work. Sorry.":
"%s requiere php 5.3.0 o superior para funcionar. Lo siento.",
"%s requires configuration section [%s] to be present in configuration file.":
- "%s requiere que la sección de configuración [% s] esté presente en el archivo de configuración.",
+ "%s requiere que la sección de configuración [%s] esté presente en el archivo de configuración.",
"Please wait %d seconds between each post.":
"Por favor espere %d segundos entre cada publicación.",
"Paste is limited to %s of encrypted data.":
diff --git a/i18n/pt.json b/i18n/pt.json
new file mode 100644
index 00000000..e00a4a15
--- /dev/null
+++ b/i18n/pt.json
@@ -0,0 +1,151 @@
+{
+ "PrivateBin": "PrivateBin",
+ "%s 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 bits AES. More information on the project page.":
+ "%s é um serviço minimalista e de código aberto do tipo \"pastebin\", em que o servidor tem zero conhecimento dos dados copiados. Os dados são cifrados e decifrados no navegador usando 256 bits AES. Mais informações na página do projeto.",
+ "Because ignorance is bliss":
+ "Porque a ignorância é uma benção",
+ "en": "pt",
+ "Paste does not exist, has expired or has been deleted.":
+ "A cópia não existe, expirou ou já foi excluída.",
+ "%s requires php 5.3.0 or above to work. Sorry.":
+ "%s requer php 5.3.0 ou superior para funcionar. Desculpa.",
+ "%s requires configuration section [%s] to be present in configuration file.":
+ "%s requer que a seção de configuração [% s] esteja no arquivo de configuração.",
+ "Please wait %d seconds between each post.":
+ "Por favor espere %d segundos entre cada publicação.",
+ "Paste is limited to %s of encrypted data.":
+ "A cópia está limitada a %s de dados cifrados.",
+ "Invalid data.":
+ "Dados inválidos.",
+ "You are unlucky. Try again.":
+ "Você é azarado. Tente novamente",
+ "Error saving comment. Sorry.":
+ "Erro ao salvar comentário. Desculpa.",
+ "Error saving paste. Sorry.":
+ "Erro ao salvar cópia. Desculpa.",
+ "Invalid paste ID.":
+ "ID de cópia inválido.",
+ "Paste is not of burn-after-reading type.":
+ "Cópia não é do tipo \"queime após ler\".",
+ "Wrong deletion token. Paste was not deleted.":
+ "Token de remoção inválido. A cópia não foi excluída.",
+ "Paste was properly deleted.":
+ "A cópia foi devidamente excluída.",
+ "JavaScript is required for %s to work.
Sorry for the inconvenience.":
+ "JavaScript é necessário para que %s funcione.
Pedimos desculpas pela inconveniência.",
+ "%s requires a modern browser to work.":
+ "%s requer um navegador moderno para funcionar.",
+ "Still using Internet Explorer? Do yourself a favor, switch to a modern browser:":
+ "Ainda usando Internet Explorer? Faça-se um favor, mude para um navegador moderno:",
+ "New":
+ "Novo",
+ "Send":
+ "Enviar",
+ "Clone":
+ "Clonar",
+ "Raw text":
+ "Texto sem formato",
+ "Expires":
+ "Expirar em",
+ "Burn after reading":
+ "Queime após ler",
+ "Open discussion":
+ "Discussão aberta",
+ "Password (recommended)":
+ "Senha (recomendada)",
+ "Discussion":
+ "Discussão",
+ "Toggle navigation":
+ "Mudar navegação",
+ "%d seconds": ["%d segundo", "%d segundos"],
+ "%d minutes": ["%d minuto", "%d minutos"],
+ "%d hours": ["%d hora", "%d horas"],
+ "%d days": ["%d dia", "%d dias"],
+ "%d weeks": ["%d semana", "%d semanas"],
+ "%d months": ["%d mês", "%d meses"],
+ "%d years": ["%d ano", "%d anos"],
+ "Never":
+ "Nunca",
+ "Note: This is a test service: Data may be deleted anytime. Kittens will die if you abuse this service.":
+ "Nota: Este é um serviço de teste. Dados podem ser perdidos a qualquer momento. Gatinhos morrerão se você abusar desse serviço.",
+ "This document will expire in %d seconds.":
+ ["Este documento irá expirar em um segundo.", "Este documento irá expirar em %d segundos."],
+ "This document will expire in %d minutes.":
+ ["Este documento irá expirar em um minuto.", "Este documento irá expirar em %d minutos."],
+ "This document will expire in %d hours.":
+ ["Este documento irá expirar em uma hora.", "Este documento irá expirar em %d horas."],
+ "This document will expire in %d days.":
+ ["Este documento irá expirar em um dia.", "Este documento irá expirar em %d dias."],
+ "This document will expire in %d months.":
+ ["Este documento irá expirar em um mês.", "Este documento irá expirar em %d meses."],
+ "Please enter the password for this paste:":
+ "Por favor, digite a senha para essa cópia:",
+ "Could not decrypt data (Wrong key?)":
+ "Não foi possível decifrar os dados (Chave errada?)",
+ "Could not delete the paste, it was not stored in burn after reading mode.":
+ "Não foi possível excluir a cópia, ela não foi salva no modo de \"queime após ler\".",
+ "FOR YOUR EYES ONLY. Don't close this window, this message can't be displayed again.":
+ "APENAS PARA SEUS OLHOS. Não feche essa janela, essa mensagem não pode ser exibida novamente.",
+ "Could not decrypt comment; Wrong key?":
+ "Não foi possível decifrar o comentário; Chave errada?",
+ "Reply":
+ "Responder",
+ "Anonymous":
+ "Anônimo",
+ "Avatar generated from IP address":
+ "Avatar (do endereço IP)",
+ "Add comment":
+ "Adicionar comentário",
+ "Optional nickname…":
+ "Apelido opcional…",
+ "Post comment":
+ "Publicar comentário",
+ "Sending comment…":
+ "Enviando comentário…",
+ "Comment posted.":
+ "Comentário publicado.",
+ "Could not refresh display: %s":
+ "Não foi possível atualizar a tela: %s",
+ "unknown status":
+ "Estado desconhecido",
+ "server error or not responding":
+ "Servidor em erro ou não responsivo",
+ "Could not post comment: %s":
+ "Não foi possível publicar o comentário: %s",
+ "Please move your mouse for more entropy…":
+ "Por favor, mova o mouse para maior entropia…",
+ "Sending paste…":
+ "Enviando cópia…",
+ "Your paste is %s (Hit [Ctrl]+[c] to copy)":
+ "Sua cópia é %s (Pressione [Ctrl]+[c] para copiar)",
+ "Delete data":
+ "Excluir dados",
+ "Could not create paste: %s":
+ "Não foi possível criar cópia: %s",
+ "Cannot decrypt paste: Decryption key missing in URL (Did you use a redirector or an URL shortener which strips part of the URL?)":
+ "Não foi possível decifrar a cópia: chave de decriptografia ausente na URL (Você utilizou um redirecionador ou encurtador de URL que removeu parte dela?)",
+ "Format": "Formato",
+ "Plain Text": "Texto sem formato",
+ "Source Code": "Código fonte",
+ "Markdown": "Markdown",
+ "Download attachment": "Baixar anexo",
+ "Cloned: '%s'": "Clonado: '%s'",
+ "Attach a file": "Anexar um arquivo",
+ "Remove attachment": "Remover anexo",
+ "Your browser does not support uploading encrypted files. Please use a newer browser.":
+ "Seu navegador não permite subir arquivos cifrados. Por favor, utilize um navegador mais recente.",
+ "Invalid attachment.": "Anexo inválido.",
+ "Options": "Opções",
+ "Shorten URL": "Encurtar URL",
+ "Editor": "Editor",
+ "Preview": "Visualizar",
+ "%s requires the PATH to end in a \"%s\". Please update the PATH in your index.php.":
+ "%s requer que o PATH termine em \"%s\". Por favor, atualize o PATH em seu index.php.",
+ "Decrypt":
+ "Decifrar",
+ "Enter password":
+ "Digite a senha",
+ "Loading…": "Carregando…",
+ "In case this message never disappears please have a look at this FAQ for information to troubleshoot.":
+ "Caso essa mensagem nunca desapareça, por favor veja este FAQ para saber como resolver os problemas."
+}
diff --git a/js/privatebin.js b/js/privatebin.js
index 1310e474..573ec9f0 100644
--- a/js/privatebin.js
+++ b/js/privatebin.js
@@ -36,11 +36,9 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
/**
* static Helper methods
*
- * @param {object} window
- * @param {object} document
* @class
*/
- var Helper = (function (window, document) {
+ var Helper = (function () {
var me = {};
/**
@@ -278,14 +276,19 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
return baseUri;
}
- // get official base uri string, from base tag in head of HTML
- baseUri = document.baseURI;
+ // window.baseURI isn't emulated by JSdom
+ var loc = window.location;
+ baseUri = loc.href.substring(
+ 0,
+ loc.href.length - loc.search.length - loc.hash.length
+ );
// if base uri contains query string (when no base tag is present),
// it is unwanted
- if (baseUri.indexOf('?')) {
+ var queryIndex = baseUri.indexOf('?');
+ if (queryIndex !== -1) {
// so we built our own baseuri
- baseUri = window.location.origin + window.location.pathname;
+ baseUri = baseUri.substring(0, queryIndex);
}
return baseUri;
@@ -307,8 +310,19 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
});
}
+ /**
+ * resets state, used for unit testing
+ *
+ * @name Model.reset
+ * @function
+ */
+ me.reset = function()
+ {
+ baseUri = null;
+ }
+
return me;
- })(window, document);
+ })();
/**
* internationalization module
@@ -336,7 +350,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
* @prop {string[]}
* @readonly
*/
- var supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'oc', 'ru', 'sl', 'zh'];
+ var supportedLanguages = ['de', 'es', 'fr', 'it', 'no', 'pl', 'pt', 'oc', 'ru', 'sl', 'zh'];
/**
* built in language
@@ -491,7 +505,7 @@ jQuery.PrivateBin = function($, sjcl, Base64, RawDeflate) {
return (n % 10 === 1 && n % 100 !== 11 ? 0 : (n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 : 2));
case 'sl':
return (n % 100 === 1 ? 1 : (n % 100 === 2 ? 2 : (n % 100 === 3 || n % 100 === 4 ? 3 : 0)));
- // de, en, es, it, no
+ // de, en, es, it, no, pt
default:
return (n !== 1 ? 1 : 0);
}
diff --git a/js/test.js b/js/test.js
index 66a44bbc..31da19fe 100644
--- a/js/test.js
+++ b/js/test.js
@@ -67,6 +67,10 @@ describe('Helper', function () {
});
describe('baseUri', function () {
+ before(function () {
+ $.PrivateBin.Helper.reset();
+ });
+
jsc.property(
'returns the URL without query & fragment',
jsc.nearray(jsc.elements(a2zString)),
@@ -77,6 +81,7 @@ describe('Helper', function () {
var expected = schema.join('') + '://' + address.join('') + '/',
clean = jsdom('', {url: expected + '?' + query.join('') + '#' + fragment}),
result = $.PrivateBin.Helper.baseUri();
+ $.PrivateBin.Helper.reset();
clean();
return expected === result;
}
@@ -101,6 +106,10 @@ describe('Helper', function () {
describe('Model', function () {
describe('getPasteId', function () {
+ before(function () {
+ $.PrivateBin.Model.reset();
+ });
+
jsc.property(
'returns the query string without separator, if any',
jsc.nearray(jsc.elements(a2zString)),
diff --git a/lib/Filter.php b/lib/Filter.php
index 60f6f170..951e2651 100644
--- a/lib/Filter.php
+++ b/lib/Filter.php
@@ -21,21 +21,6 @@ use Exception;
*/
class Filter
{
- /**
- * strips slashes deeply
- *
- * @access public
- * @static
- * @param mixed $value
- * @return mixed
- */
- public static function stripslashesDeep($value)
- {
- return is_array($value) ?
- array_map('self::stripslashesDeep', $value) :
- stripslashes($value);
- }
-
/**
* format a given time string into a human readable label (localized)
*
diff --git a/lib/I18n.php b/lib/I18n.php
index 4c59ef57..d35bcf01 100644
--- a/lib/I18n.php
+++ b/lib/I18n.php
@@ -304,7 +304,7 @@ class I18n
return $n % 10 == 1 && $n % 100 != 11 ? 0 : ($n % 10 >= 2 && $n % 10 <= 4 && ($n % 100 < 10 || $n % 100 >= 20) ? 1 : 2);
case 'sl':
return $n % 100 == 1 ? 1 : ($n % 100 == 2 ? 2 : ($n % 100 == 3 || $n % 100 == 4 ? 3 : 0));
- // de, en, es, it, no
+ // de, en, es, it, no, pt
default:
return $n != 1 ? 1 : 0;
}
diff --git a/lib/PrivateBin.php b/lib/PrivateBin.php
index fc69e57c..fb3e523f 100644
--- a/lib/PrivateBin.php
+++ b/lib/PrivateBin.php
@@ -120,8 +120,8 @@ class PrivateBin
*/
public function __construct()
{
- if (version_compare(PHP_VERSION, '5.3.0') < 0) {
- throw new Exception(I18n::_('%s requires php 5.3.0 or above to work. Sorry.', I18n::_('PrivateBin')), 1);
+ if (version_compare(PHP_VERSION, '5.4.0') < 0) {
+ throw new Exception(I18n::_('%s requires php 5.4.0 or above to work. Sorry.', I18n::_('PrivateBin')), 1);
}
if (strlen(PATH) < 0 && substr(PATH, -1) !== DIRECTORY_SEPARATOR) {
throw new Exception(I18n::_('%s requires the PATH to end in a "%s". Please update the PATH in your index.php.', I18n::_('PrivateBin'), DIRECTORY_SEPARATOR), 5);
diff --git a/lib/Request.php b/lib/Request.php
index d3c36d38..e6c1c749 100644
--- a/lib/Request.php
+++ b/lib/Request.php
@@ -80,13 +80,6 @@ class Request
*/
public function __construct()
{
- // in case stupid admin has left magic_quotes enabled in php.ini (for PHP < 5.4)
- if (version_compare(PHP_VERSION, '5.4.0') < 0 && get_magic_quotes_gpc()) {
- $_POST = array_map('PrivateBin\\Filter::stripslashesDeep', $_POST);
- $_GET = array_map('PrivateBin\\Filter::stripslashesDeep', $_GET);
- $_COOKIE = array_map('PrivateBin\\Filter::stripslashesDeep', $_COOKIE);
- }
-
// decide if we are in JSON API or HTML context
$this->_isJsonApi = $this->_detectJsonRequest();
diff --git a/tpl/bootstrap.php b/tpl/bootstrap.php
index 9fbc1156..0c0e51c5 100644
--- a/tpl/bootstrap.php
+++ b/tpl/bootstrap.php
@@ -69,7 +69,7 @@ if ($MARKDOWN):
-
+
@@ -406,8 +406,7 @@ endif;