From 06fef4318047ed8594ba0533ab228ee5838cd86f Mon Sep 17 00:00:00 2001
From: NumEricR
Date: Sat, 5 Oct 2013 10:51:25 +0200
Subject: [PATCH 01/24] Fix #255 : increase article toolbar height if necessary
---
themes/default/css/style.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/default/css/style.css b/themes/default/css/style.css
index eb04f0c48..3ef9c8236 100644
--- a/themes/default/css/style.css
+++ b/themes/default/css/style.css
@@ -188,7 +188,7 @@ a:visited {
bottom: 0;
left: 0;
width: 100%;
- height: 50px;
+ min-height: 50px;
padding-top: 17px;
text-align: center;
color: #fff;
From 89812ec81cf77af32942d151372a8667c716fe30 Mon Sep 17 00:00:00 2001
From: NumEricR
Date: Sat, 5 Oct 2013 10:52:08 +0200
Subject: [PATCH 02/24] Remove useless filter on .git folder
---
inc/poche/Poche.class.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 1ba8e7c14..1a6553866 100644
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -258,7 +258,7 @@ class Poche
while (($theme = readdir($handle)) !== false) {
# Themes are stored in a directory, so all directory names are themes
# @todo move theme installation data to database
- if (! is_dir(THEME . '/' . $theme) || in_array($theme, array('..', '.', '.git'))) {
+ if (! is_dir(THEME . '/' . $theme) || in_array($theme, array('..', '.'))) {
continue;
}
From 2287bf06f533e1dc03979d3945af098601963712 Mon Sep 17 00:00:00 2001
From: NumEricR
Date: Sat, 5 Oct 2013 11:03:41 +0200
Subject: [PATCH 03/24] Sort themes alphabetically in config list
---
inc/poche/Poche.class.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 1a6553866..6325adcf2 100644
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -271,6 +271,7 @@ class Poche
$themes[] = array('name' => $theme, 'current' => $current);
}
+ sort($themes);
return $themes;
}
From 125f9ee83842cd6ad6cfcbcbfcaa951ec0396733 Mon Sep 17 00:00:00 2001
From: Jean-Christophe Saad-Dupuy
Date: Mon, 7 Oct 2013 12:30:40 +0200
Subject: [PATCH 04/24] Added support for custom ssl port
---
inc/poche/Tools.class.php | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php
index 8eb988f46..6f2bc6e72 100644
--- a/inc/poche/Tools.class.php
+++ b/inc/poche/Tools.class.php
@@ -41,10 +41,14 @@ class Tools
$https = (!empty($_SERVER['HTTPS'])
&& (strtolower($_SERVER['HTTPS']) == 'on'))
|| (isset($_SERVER["SERVER_PORT"])
- && $_SERVER["SERVER_PORT"] == '443'); // HTTPS detection.
+ && $_SERVER["SERVER_PORT"] == '443') // HTTPS detection.
+ || (isset($_SERVER["SERVER_PORT"]) && isset($SSL_PORT) //Custom HTTPS port detection
+ && $_SERVER["SERVER_PORT"] == $SSL_PORT);
+
$serverport = (!isset($_SERVER["SERVER_PORT"])
|| $_SERVER["SERVER_PORT"] == '80'
|| ($https && $_SERVER["SERVER_PORT"] == '443')
+ || ($https && $_SERVER["SERVER_PORT"]==$SSL_PORT) //Custom HTTPS port detection
? '' : ':' . $_SERVER["SERVER_PORT"]);
$scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]);
@@ -243,4 +247,4 @@ class Tools
$lang = explode('.', $userlanguage);
return str_replace('_', '-', $lang[0]);
}
-}
\ No newline at end of file
+}
From 93eed125058574ef407e2edede6740629f7f0a43 Mon Sep 17 00:00:00 2001
From: Jean-Christophe Saad-Dupuy
Date: Mon, 7 Oct 2013 12:31:46 +0200
Subject: [PATCH 05/24] Fixed typo
---
themes/default/_head.twig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/default/_head.twig b/themes/default/_head.twig
index dfebd91d6..9745459c4 100644
--- a/themes/default/_head.twig
+++ b/themes/default/_head.twig
@@ -8,4 +8,4 @@
-
+
From 2621569200cfc85f2dd6fb77481c9531942c0642 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20L=C5=93uillet?=
Date: Mon, 7 Oct 2013 12:39:11 +0200
Subject: [PATCH 06/24] missing }
---
themes/default/_head.twig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/default/_head.twig b/themes/default/_head.twig
index dfebd91d6..9745459c4 100644
--- a/themes/default/_head.twig
+++ b/themes/default/_head.twig
@@ -8,4 +8,4 @@
-
+
From 2916d24b209e1e36cf22cb902d7e6dc34113c7e5 Mon Sep 17 00:00:00 2001
From: Jean-Christophe Saad-Dupuy
Date: Mon, 7 Oct 2013 12:47:13 +0200
Subject: [PATCH 07/24] Added support for custom SSL port
---
inc/poche/Tools.class.php | 6 +++---
inc/poche/config.inc.php.new | 5 ++++-
2 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/inc/poche/Tools.class.php b/inc/poche/Tools.class.php
index 6f2bc6e72..1d092823f 100644
--- a/inc/poche/Tools.class.php
+++ b/inc/poche/Tools.class.php
@@ -42,13 +42,13 @@ class Tools
&& (strtolower($_SERVER['HTTPS']) == 'on'))
|| (isset($_SERVER["SERVER_PORT"])
&& $_SERVER["SERVER_PORT"] == '443') // HTTPS detection.
- || (isset($_SERVER["SERVER_PORT"]) && isset($SSL_PORT) //Custom HTTPS port detection
- && $_SERVER["SERVER_PORT"] == $SSL_PORT);
+ || (isset($_SERVER["SERVER_PORT"]) //Custom HTTPS port detection
+ && $_SERVER["SERVER_PORT"] == SSL_PORT);
$serverport = (!isset($_SERVER["SERVER_PORT"])
|| $_SERVER["SERVER_PORT"] == '80'
|| ($https && $_SERVER["SERVER_PORT"] == '443')
- || ($https && $_SERVER["SERVER_PORT"]==$SSL_PORT) //Custom HTTPS port detection
+ || ($https && $_SERVER["SERVER_PORT"]==SSL_PORT) //Custom HTTPS port detection
? '' : ':' . $_SERVER["SERVER_PORT"]);
$scriptname = str_replace('/index.php', '/', $_SERVER["SCRIPT_NAME"]);
diff --git a/inc/poche/config.inc.php.new b/inc/poche/config.inc.php.new
index 255b97e6c..902509e3d 100755
--- a/inc/poche/config.inc.php.new
+++ b/inc/poche/config.inc.php.new
@@ -25,6 +25,9 @@ define ('STORAGE_PASSWORD', 'poche');
# Do not trespass unless you know what you are doing
#################################################################################
+// Change this if not using the standart port for SSL - i.e you server is behind sslh
+define ('SSL_PORT', 443);
+
define ('MODE_DEMO', FALSE);
define ('DEBUG_POCHE', FALSE);
define ('DOWNLOAD_PICTURES', FALSE);
@@ -57,4 +60,4 @@ define ('INSTAPAPER_FILE', '/instapaper-export.html');
define ('IMPORT_POCKET_FILE', ROOT . POCKET_FILE);
define ('IMPORT_READABILITY_FILE', ROOT . READABILITY_FILE);
-define ('IMPORT_INSTAPAPER_FILE', ROOT . INSTAPAPER_FILE);
\ No newline at end of file
+define ('IMPORT_INSTAPAPER_FILE', ROOT . INSTAPAPER_FILE);
From 5eebe4e50dd5da7e87e98c366205ed3266348603 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20L=C5=93uillet?=
Date: Mon, 7 Oct 2013 13:06:10 +0200
Subject: [PATCH 08/24] delete href
---
themes/default/_head.twig | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/themes/default/_head.twig b/themes/default/_head.twig
index 9745459c4..247b929eb 100644
--- a/themes/default/_head.twig
+++ b/themes/default/_head.twig
@@ -8,4 +8,4 @@
-
+
From 9d3b88b3796496b68e762d50904a7ab609edc9c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20L=C5=93uillet?=
Date: Mon, 7 Oct 2013 13:12:28 +0200
Subject: [PATCH 09/24] bug fix #266: make installation steps easier
---
inc/poche/Poche.class.php | 85 ++++++++++++++++++++-------------------
index.php | 7 +++-
themes/default/error.twig | 9 ++++-
3 files changed, 57 insertions(+), 44 deletions(-)
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 1ba8e7c14..899d73567 100644
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -20,7 +20,7 @@ class Poche
public $pagination;
private $currentTheme = '';
- private $notInstalledMessage = '';
+ private $notInstalledMessage = array();
# @todo make this dynamic (actually install themes and save them in the database including author information et cetera)
private $installedThemes = array(
@@ -33,28 +33,21 @@ class Poche
public function __construct()
{
- if (! $this->configFileIsAvailable()) {
- return;
+ if ($this->configFileIsAvailable()) {
+ $this->init();
}
- $this->init();
-
- if (! $this->themeIsInstalled()) {
- return;
+ if ($this->themeIsInstalled()) {
+ $this->initTpl();
}
- $this->initTpl();
-
- if (! $this->systemIsInstalled()) {
- return;
- }
-
- $this->store = new Database();
- $this->messages = new Messages();
-
- # installation
- if (! $this->store->isInstalled()) {
- $this->install();
+ if ($this->systemIsInstalled()) {
+ $this->store = new Database();
+ $this->messages = new Messages();
+ # installation
+ if (! $this->store->isInstalled()) {
+ $this->install();
+ }
}
}
@@ -94,7 +87,7 @@ class Poche
public function configFileIsAvailable() {
if (! self::$configFileAvailable) {
- $this->notInstalledMessage = 'You have to rename inc/poche/config.inc.php.new to inc/poche/config.inc.php.';
+ $this->notInstalledMessage[] = 'You have to rename inc/poche/config.inc.php.new to inc/poche/config.inc.php.';
return false;
}
@@ -103,39 +96,44 @@ class Poche
}
public function themeIsInstalled() {
+ $passTheme = TRUE;
# Twig is an absolute requirement for Poche to function. Abort immediately if the Composer installer hasn't been run yet
if (! self::$canRenderTemplates) {
- $this->notInstalledMessage = 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. Have a look at the documentation.';
-
- return false;
+ $this->notInstalledMessage[] = 'Twig does not seem to be installed. Please initialize the Composer installation to automatically fetch dependencies. Have a look at the documentation.';
+ $passTheme = FALSE;
}
if (! is_writable(CACHE)) {
- $this->notInstalledMessage = 'error
You don\'t have write access on cache directory.
';
+ $this->notInstalledMessage[] = 'You don\'t have write access on cache directory.';
self::$canRenderTemplates = false;
- return false;
+ $passTheme = FALSE;
}
# Check if the selected theme and its requirements are present
- if (! is_dir(THEME . '/' . $this->getTheme())) {
- $this->notInstalledMessage = 'The currently selected theme (' . $this->getTheme() . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $this->getTheme() . ')';
+ if ($this->getTheme() != '' && ! is_dir(THEME . '/' . $this->getTheme())) {
+ $this->notInstalledMessage[] = 'The currently selected theme (' . $this->getTheme() . ') does not seem to be properly installed (Missing directory: ' . THEME . '/' . $this->getTheme() . ')';
self::$canRenderTemplates = false;
- return false;
+ $passTheme = FALSE;
}
foreach ($this->installedThemes[$this->getTheme()]['requires'] as $requiredTheme) {
if (! is_dir(THEME . '/' . $requiredTheme)) {
- $this->notInstalledMessage = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $this->getTheme() . ')';
+ $this->notInstalledMessage[] = 'The required "' . $requiredTheme . '" theme is missing for the current theme (' . $this->getTheme() . ')';
self::$canRenderTemplates = false;
- return false;
+ $passTheme = FALSE;
}
}
+
+ if (!$passTheme) {
+ return FALSE;
+ }
+
return true;
}
@@ -147,25 +145,30 @@ class Poche
*/
public function systemIsInstalled()
{
- $msg = '';
+ $msg = TRUE;
$configSalt = defined('SALT') ? constant('SALT') : '';
if (empty($configSalt)) {
- $msg = 'error
You have not yet filled in the SALT value in the config.inc.php file.
';
- } else if (STORAGE == 'sqlite' && ! file_exists(STORAGE_SQLITE)) {
+ $this->notInstalledMessage[] = 'You have not yet filled in the SALT value in the config.inc.php file.';
+ $msg = FALSE;
+ }
+ if (STORAGE == 'sqlite' && ! file_exists(STORAGE_SQLITE)) {
Tools::logm('sqlite file doesn\'t exist');
- $msg = 'error
sqlite file doesn\'t exist, you can find it in install folder. Copy it in /db folder.
';
- } else if (is_dir(ROOT . '/install') && ! DEBUG_POCHE) {
- $msg = 'install folder
you have to delete the /install folder before using poche.
';
- } else if (STORAGE == 'sqlite' && ! is_writable(STORAGE_SQLITE)) {
+ $this->notInstalledMessage[] = 'sqlite file doesn\'t exist, you can find it in install folder. Copy it in /db folder.';
+ $msg = FALSE;
+ }
+ if (is_dir(ROOT . '/install') && ! DEBUG_POCHE) {
+ $this->notInstalledMessage[] = 'you have to delete the /install folder before using poche.';
+ $msg = FALSE;
+ }
+ if (STORAGE == 'sqlite' && ! is_writable(STORAGE_SQLITE)) {
Tools::logm('you don\'t have write access on sqlite file');
- $msg = 'error
You don\'t have write access on sqlite file.
';
+ $this->notInstalledMessage[] = 'You don\'t have write access on sqlite file.';
+ $msg = FALSE;
}
- if (! empty($msg)) {
- $this->notInstalledMessage = $msg;
-
+ if (! $msg) {
return false;
}
diff --git a/index.php b/index.php
index 4aebfe10a..f1953c2cc 100644
--- a/index.php
+++ b/index.php
@@ -35,7 +35,12 @@ $tpl_vars = array(
if (! empty($notInstalledMessage)) {
if (! Poche::$canRenderTemplates || ! Poche::$configFileAvailable) {
# We cannot use Twig to display the error message
- die($notInstalledMessage);
+ echo 'Errors
';
+ foreach ($notInstalledMessage as $message) {
+ echo '- ' . $message . '
';
+ }
+ echo '
';
+ die();
} else {
# Twig is installed, put the error message in the template
$tpl_file = Tools::getTplFile('error');
diff --git a/themes/default/error.twig b/themes/default/error.twig
index c829d12bb..99eb1ed65 100644
--- a/themes/default/error.twig
+++ b/themes/default/error.twig
@@ -1,6 +1,11 @@
{% extends "layout.twig" %}
{% block title %}{% trans "plop" %}{% endblock %}
{% block content %}
- {{ msg|raw }}
- Don't forget the documentation.
+ Errors
+
+ {% for message in msg %}
+ - {{message}}
+ {% endfor %}
+
+ Don't forget the documentation.
{% endblock %}
\ No newline at end of file
From 031df528b611a5c6639c0d86636633da098d8aa2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Nicolas=20L=C5=93uillet?=
Date: Mon, 7 Oct 2013 13:19:34 +0200
Subject: [PATCH 10/24] bug fix #268: move POCHE_VERSION in index.php and
change the name to avoid conflicts when updating
---
inc/poche/Poche.class.php | 4 +-
inc/poche/config.inc.php.new | 2 -
index.php | 1 +
install/update_sqlite_from_0_to_1.php | 72 ------------------------
install/update_to_1beta3.php | 79 ---------------------------
themes/default/_footer.twig | 2 +-
themes/default/config.twig | 4 +-
7 files changed, 6 insertions(+), 158 deletions(-)
delete mode 100644 install/update_sqlite_from_0_to_1.php
delete mode 100644 install/update_to_1beta3.php
diff --git a/inc/poche/Poche.class.php b/inc/poche/Poche.class.php
index 899d73567..245f6a138 100644
--- a/inc/poche/Poche.class.php
+++ b/inc/poche/Poche.class.php
@@ -366,8 +366,8 @@ class Poche
case 'config':
$dev = $this->getPocheVersion('dev');
$prod = $this->getPocheVersion('prod');
- $compare_dev = version_compare(POCHE_VERSION, $dev);
- $compare_prod = version_compare(POCHE_VERSION, $prod);
+ $compare_dev = version_compare(POCHE, $dev);
+ $compare_prod = version_compare(POCHE, $prod);
$themes = $this->getInstalledThemes();
$tpl_vars = array(
'themes' => $themes,
diff --git a/inc/poche/config.inc.php.new b/inc/poche/config.inc.php.new
index 255b97e6c..99223c785 100755
--- a/inc/poche/config.inc.php.new
+++ b/inc/poche/config.inc.php.new
@@ -49,8 +49,6 @@ define ('CACHE', ROOT . '/cache');
define ('PAGINATION', '10');
-define ('POCHE_VERSION', '1.0.0');
-
define ('POCKET_FILE', '/ril_export.html');
define ('READABILITY_FILE', '/readability');
define ('INSTAPAPER_FILE', '/instapaper-export.html');
diff --git a/index.php b/index.php
index f1953c2cc..22696c6fd 100644
--- a/index.php
+++ b/index.php
@@ -8,6 +8,7 @@
* @license http://www.wtfpl.net/ see COPYING file
*/
+define ('POCHE', '1.0.0');
require_once 'inc/poche/global.inc.php';
# Start Poche
diff --git a/install/update_sqlite_from_0_to_1.php b/install/update_sqlite_from_0_to_1.php
deleted file mode 100644
index 299abf48d..000000000
--- a/install/update_sqlite_from_0_to_1.php
+++ /dev/null
@@ -1,72 +0,0 @@
-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
-# Requêtes à exécuter pour mettre à jour poche.sqlite en 1.x
-
-# ajout d'un champ user_id sur la table entries
-$sql = 'ALTER TABLE entries RENAME TO tempEntries;';
-$query = $handle->prepare($sql);
-$query->execute();
-
-$sql = 'CREATE TABLE entries (id INTEGER PRIMARY KEY, title TEXT, url TEXT, is_read NUMERIC DEFAULT 0, is_fav NUMERIC DEFAULT 0, content BLOB, user_id NUMERIC);';
-$query = $handle->prepare($sql);
-$query->execute();
-
-$sql = 'INSERT INTO entries (id, title, url, is_read, is_fav, content) SELECT id, title, url, is_read, is_fav, content FROM tempEntries;';
-$query = $handle->prepare($sql);
-$query->execute();
-
-# Update tout pour mettre user_id = 1
-$sql = 'UPDATE entries SET user_id = 1;';
-$query = $handle->prepare($sql);
-$query->execute();
-
-# Changement des flags pour les lus / favoris
-$sql = 'UPDATE entries SET is_read = 1 WHERE is_read = -1;';
-$query = $handle->prepare($sql);
-$query->execute();
-
-$sql = 'UPDATE entries SET is_fav = 1 WHERE is_fav = -1;';
-$query = $handle->prepare($sql);
-$query->execute();
-
-# Création de la table users
-$sql = 'CREATE TABLE users (id INTEGER PRIMARY KEY, username TEXT, password TEXT, name TEXT, email TEXT);';
-$query = $handle->prepare($sql);
-$query->execute();
-
-$sql = 'INSERT INTO users (username) SELECT value FROM config WHERE name = "login";';
-$query = $handle->prepare($sql);
-$query->execute();
-
-$sql = "UPDATE users SET password = (SELECT value FROM config WHERE name = 'password')";
-$query = $handle->prepare($sql);
-$query->execute();
-
-# Création de la table users_config
-$sql = 'CREATE TABLE users_config (id INTEGER PRIMARY KEY, user_id NUMERIC, name TEXT, value TEXT);';
-$query = $handle->prepare($sql);
-$query->execute();
-
-$sql = 'INSERT INTO users_config (user_id, name, value) VALUES (1, "pager", "10");';
-$query = $handle->prepare($sql);
-$query->execute();
-
-$sql = 'INSERT INTO users_config (user_id, name, value) VALUES (1, "language", "en_EN.UTF8");';
-$query = $handle->prepare($sql);
-$query->execute();
-
-# Suppression de la table temporaire
-$sql = 'DROP TABLE tempEntries;';
-$query = $handle->prepare($sql);
-$query->execute();
-
-# Vidage de la table de config
-$sql = 'DELETE FROM config;';
-$query = $handle->prepare($sql);
-$query->execute();
-
-echo 'welcome to poche 1.0 !';
\ No newline at end of file
diff --git a/install/update_to_1beta3.php b/install/update_to_1beta3.php
deleted file mode 100644
index e0da1590d..000000000
--- a/install/update_to_1beta3.php
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
-
-
-
-
-
-
-
- updating poche
-
-
- update poche to 1.0-beta3
-
- Changelog
-
-
- - this awesome updating step
- - error message when install folder exists
- - more tests before installation (write access, etc.)
- - updated README to make installation easier
- - german language thanks to HLFH
- - spanish language thanks to Nitche
- - new file ./inc/poche/myconfig.inc.php created to store language and salt
- - #119: salt is now created when installing poche
- - #130: robots.txt added
- - #136: error during readability import
- - #137: mixed content alert in https
- - #138: change pattern to parse url with #
-
-
- To update your poche, please fill the following fields.
-
-
-
-login($_POST['login'], sha1($_POST['password'] . $_POST['login'] . $old_salt));
- if ($user != array()) {
- $new_salt = md5(time() . $_SERVER['SCRIPT_FILENAME'] . rand());
- $myconfig_file = '../inc/poche/myconfig.inc.php';
- if (!is_writable('../inc/poche/')) {
- die('You don\'t have write access to create ./inc/poche/myconfig.inc.php.');
- }
-
- if (!file_exists($myconfig_file))
- {
- $fp = fopen($myconfig_file, 'w');
-
- fwrite($fp, 'updatePassword($user['id'], sha1($_POST['password'] . $_POST['login'] . $new_salt));
-?>
- your poche is up to date!
- don't forget to delete ./install/ folder after the update.
- go back to your poche
-
-
-
\ No newline at end of file
diff --git a/themes/default/_footer.twig b/themes/default/_footer.twig
index 2b8958549..06148a57c 100644
--- a/themes/default/_footer.twig
+++ b/themes/default/_footer.twig
@@ -1,4 +1,4 @@
\ No newline at end of file
diff --git a/themes/default/config.twig b/themes/default/config.twig
index 0f1aea80b..af8ff8ca7 100644
--- a/themes/default/config.twig
+++ b/themes/default/config.twig
@@ -5,7 +5,7 @@
{% include '_menu.twig' %}
{% endblock %}
{% block content %}
- {% trans "Poching a link" %}
+ {% trans "Poching a link" %}
F
{% trans "You can poche a link by several methods:" %} (?)