diff --git a/attachment/attachment.php b/attachment/attachment.php
index 412376c16..cdecc2a82 100644
--- a/attachment/attachment.php
+++ b/attachment/attachment.php
@@ -95,7 +95,7 @@ protected function configure_properties()
'physical_filename' => array('default' => '', 'max' => 255),
'attachment_directory' => array('default' => '', 'max' => 255),
'real_filename' => array('default' => '', 'max' => 255),
- 'attachment_comment' => array('default' => ''),
+ 'attachment_comment' => array('default' => '', 'encode_ucr' => true),
'download_count' => array('default' => 0),
@@ -202,6 +202,14 @@ public function submit($data = null)
if (!empty($data))
{
+ foreach ($data as $name => $value)
+ {
+ if (isset($this->object_config[$name]))
+ {
+ $data[$name] = $this->validate_property($value, $this->object_config[$name]);
+ }
+ }
+
$sql = 'UPDATE ' . $this->sql_table . '
SET ' . $this->db->sql_build_array('UPDATE', $data) . '
WHERE attachment_id = ' . $this->get_id();
diff --git a/attachment/uploader.php b/attachment/uploader.php
index d22820f02..067e8f72e 100644
--- a/attachment/uploader.php
+++ b/attachment/uploader.php
@@ -16,6 +16,7 @@
use phpbb\files\upload;
use phpbb\request\request_interface;
use phpbb\titania\access;
+use phpbb\titania\unicode;
use phpbb\titania\ext;
class uploader
@@ -260,6 +261,16 @@ public function upload_file()
return false;
}
+ if (unicode::contains_unsupported($file->get('uploadname')))
+ {
+ $file->error[] = $this->user->lang('INVALID_FILENAME', $file->get('uploadname'));
+ $file->remove();
+ $this->filedata['error'] = array_merge($this->filedata['error'], $file->error);
+ $this->filedata['post_attach'] = false;
+
+ return false;
+ }
+
// Set max file size for anyone but team members.
if (!$this->access->is_team())
{
diff --git a/contribution/extension/type.php b/contribution/extension/type.php
index 2d93f7398..7e8eb2040 100644
--- a/contribution/extension/type.php
+++ b/contribution/extension/type.php
@@ -18,6 +18,7 @@
use phpbb\titania\attachment\attachment;
use phpbb\titania\config\config as ext_config;
use phpbb\titania\contribution\type\base;
+use phpbb\titania\unicode;
use phpbb\titania\entity\package;
use phpbb\user;
@@ -216,7 +217,7 @@ public function epv_test(\titania_contribution $contrib, \titania_revision $revi
public function validate_ext_name($name)
{
return (bool) preg_match(
- '#^[a-zA-Z0-9\x7f-\xff]{2,}/[a-zA-Z0-9\x7f-\xff]{2,}$#',
+ '#^[a-zA-Z0-9\x{007F}-\x{FFFF}]{2,}/[a-zA-Z0-9\x{007F}-\x{FFFF}]{2,}$#u',
$name
);
}
@@ -283,10 +284,10 @@ protected function repack(package $package, \titania_contribution $contrib, \tit
$data = $this->update_phpbb_requirement($data, $revision);
$data = $this->set_version_check($data, $contrib);
- $data = json_encode(
+ $data = unicode::escape_json(json_encode(
$data,
JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
- );
+ ));
file_put_contents($composer_file, $data);
$package->restore_root($ext_base_path, $ext_name);
diff --git a/contribution/style/colorizeit_helper.php b/contribution/style/colorizeit_helper.php
index 6ee21deb5..6bef945eb 100644
--- a/contribution/style/colorizeit_helper.php
+++ b/contribution/style/colorizeit_helper.php
@@ -13,6 +13,8 @@
namespace phpbb\titania\contribution\style;
+use phpbb\titania\unicode;
+
class colorizeit_helper
{
/** @var array */
@@ -69,6 +71,14 @@ public function generate_options($zip_file, $temp_dir)
public function submit_options($options, $revision_id, $db)
{
$options = serialize($options);
+
+ // These values come from configuration files inside the uploaded style.
+ // Keep serving unsupported names without attempting to cache them.
+ if (unicode::contains_unsupported($options))
+ {
+ return;
+ }
+
$sql = 'UPDATE ' . TITANIA_REVISIONS_TABLE . '
SET revision_clr_options = "' . $db->sql_escape($options) . '"
WHERE revision_id = ' . (int) $revision_id;
diff --git a/contribution/style/type.php b/contribution/style/type.php
index ff9385281..81db1e702 100644
--- a/contribution/style/type.php
+++ b/contribution/style/type.php
@@ -19,6 +19,7 @@
use phpbb\titania\attachment\attachment;
use phpbb\titania\config\config as ext_config;
use phpbb\titania\contribution\type\base;
+use phpbb\titania\unicode;
use phpbb\titania\entity\package;
use phpbb\user;
@@ -165,7 +166,7 @@ public function upload_check(attachment $attachment)
public function fix_package_name(\titania_contribution $contrib, \titania_revision $revision, attachment $attachment, $root_dir = null)
{
// If we managed to find a single parent directory, then we use that in the zip name, otherwise we fall back to using contrib_name_clean
- if ($root_dir !== null)
+ if ($root_dir !== null && !unicode::contains_unsupported($root_dir))
{
$new_real_filename = $root_dir . '_' . strtolower($revision->revision_version) . '.' . $attachment->extension;
}
diff --git a/contribution/translation/type.php b/contribution/translation/type.php
index fdf5cbbf0..1b0b1b5b1 100644
--- a/contribution/translation/type.php
+++ b/contribution/translation/type.php
@@ -173,7 +173,7 @@ public function translation_validate(\titania_contribution $contrib, \titania_re
// Save the translation validation results (we need to save it here so that we can add it to the post later)
$queue = $revision->get_queue();
- $queue->tv_results = $translation_validator_output;
+ $queue->tv_results = utf8_encode_ucr($translation_validator_output);
$queue->submit();
return array(
diff --git a/controller/author.php b/controller/author.php
index a8d0267d5..9219a5010 100644
--- a/controller/author.php
+++ b/controller/author.php
@@ -405,6 +405,11 @@ protected function create()
));
$authors['author'] = array($this->user->data['username'] => $this->user->data['user_id']);
+ if ($contrib->contrib_name_clean === '')
+ {
+ $contrib->generate_permalink();
+ }
+
$error = $contrib->validate($settings['categories'], $authors, $settings['custom']);
if (($form_key_error = $message->validate_form_key()) !== false)
diff --git a/controller/contribution/manage.php b/controller/contribution/manage.php
index b52ce9eb7..453c3f4d1 100644
--- a/controller/contribution/manage.php
+++ b/controller/contribution/manage.php
@@ -194,6 +194,15 @@ public function manage($contrib_type, $contrib)
$this->contrib->post_data($this->message);
+ if (!$this->is_moderator)
+ {
+ $this->settings['permalink'] = $this->contrib->contrib_name_clean;
+ }
+ else if ($this->settings['permalink'] === '')
+ {
+ $this->settings['permalink'] = $this->contrib->get_generated_permalink();
+ }
+
$authors = $this->contrib->get_authors_from_usernames(array(
'active_coauthors' => $this->settings['coauthors']['active'],
'nonactive_coauthors' => $this->settings['coauthors']['nonactive'],
@@ -206,7 +215,8 @@ public function manage($contrib_type, $contrib)
$this->settings['categories'],
$authors,
$this->settings['custom'],
- $this->contrib->contrib_name_clean
+ $this->contrib->contrib_name_clean,
+ $this->settings['permalink']
));
// Did we succeed or have an error?
@@ -474,11 +484,6 @@ protected function submit($authors, $old_settings)
if ($this->settings['permalink'] != $this->contrib->contrib_name_clean)
{
- if ($this->settings['permalink'] == '')
- {
- $this->contrib->generate_permalink();
- $this->settings['permalink'] = $this->contrib->contrib_name_clean;
- }
$this->contrib->change_permalink($this->settings['permalink']);
}
}
diff --git a/controller/contribution/revision.php b/controller/contribution/revision.php
index f163ccd4a..72a8bb7ea 100644
--- a/controller/contribution/revision.php
+++ b/controller/contribution/revision.php
@@ -15,6 +15,7 @@
use phpbb\titania\composer\repository;
use phpbb\titania\contribution\type\collection as type_collection;
+use phpbb\titania\unicode;
use phpbb\titania\ext;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -589,7 +590,7 @@ protected function create_queue_item($allow_repack, $test_account)
{
$this->queue->queue_notes .=
"\n\n[b]" . $this->user->lang('TEST_ACCOUNT') . "[/b]\n" .
- $test_account
+ utf8_encode_ucr($test_account)
;
}
$this->queue->submit();
@@ -615,6 +616,20 @@ protected function validate_settings($settings)
$error[] = $this->user->lang['NO_REVISION_VERSION'];
}
+ $metadata = array_merge(
+ array($settings['name'], $settings['version'], $settings['license']),
+ $settings['custom']
+ );
+
+ foreach ($metadata as $value)
+ {
+ if (is_string($value) && unicode::contains_unsupported($value))
+ {
+ $error[] = $this->user->lang['REVISION_EMOJI_NOT_ALLOWED'];
+ break;
+ }
+ }
+
if (!empty($this->contrib->type->license_options) && !$this->contrib->type->license_allow_custom
&& !in_array($settings['license'], $this->contrib->type->license_options))
{
diff --git a/controller/contribution/revision_edit.php b/controller/contribution/revision_edit.php
index 05d7107c7..d4b53a42a 100644
--- a/controller/contribution/revision_edit.php
+++ b/controller/contribution/revision_edit.php
@@ -14,6 +14,7 @@
namespace phpbb\titania\controller\contribution;
use phpbb\titania\contribution\type\collection as type_collection;
+use phpbb\titania\unicode;
use phpbb\titania\ext;
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -243,6 +244,16 @@ protected function validate_settings($settings)
{
$error[] = $this->user->lang['FORM_INVALID'];
}
+
+ if (
+ unicode::contains_unsupported($settings['name']) ||
+ unicode::contains_unsupported($settings['license']) ||
+ unicode::contains_unsupported($settings['custom_license'])
+ )
+ {
+ $error[] = $this->user->lang['REVISION_EMOJI_NOT_ALLOWED'];
+ }
+
$license_options = ($this->contrib->type->license_options) ?: array();
if ($license_options && !$this->contrib->type->license_allow_custom && !in_array($settings['license'], $license_options))
diff --git a/entity/database_base.php b/entity/database_base.php
index b5323fb8c..f23cf470b 100644
--- a/entity/database_base.php
+++ b/entity/database_base.php
@@ -255,7 +255,21 @@ protected function validate_string($value, $config)
// Truncate to the maximum length
if (isset($config['max']) && $config['max'])
{
- truncate_string($value, $config['max']);
+ $value = truncate_string($value, $config['max']);
+ }
+
+ if (!empty($config['encode_ucr']))
+ {
+ $value = utf8_encode_ucr($value);
+
+ // Numeric references use more storage characters than the
+ // characters they represent.
+ while (!empty($config['max']) && utf8_strlen($value) > $config['max'])
+ {
+ $length = utf8_strlen(html_entity_decode($value, ENT_COMPAT, 'UTF-8'));
+ $value = truncate_string($value, $length - 1);
+ $value = utf8_encode_ucr($value);
+ }
}
return $value;
diff --git a/includes/objects/attention.php b/includes/objects/attention.php
index 6c8b8832d..a7398e087 100644
--- a/includes/objects/attention.php
+++ b/includes/objects/attention.php
@@ -66,8 +66,8 @@ public function __construct()
'attention_time' => array('default' => titania::$time),
'attention_close_time' => array('default' => 0),
'attention_close_user' => array('default' => 0),
- 'attention_title' => array('default' => ''),
- 'attention_description' => array('default' => ''),
+ 'attention_title' => array('default' => '', 'max' => 255, 'encode_ucr' => true),
+ 'attention_description' => array('default' => '', 'encode_ucr' => true),
'notify_reporter' => array('default' => 0),
));
diff --git a/includes/objects/category.php b/includes/objects/category.php
index 0062566ae..aae5372b3 100644
--- a/includes/objects/category.php
+++ b/includes/objects/category.php
@@ -11,6 +11,7 @@
*
*/
+use phpbb\titania\unicode;
use phpbb\titania\ext;
use phpbb\titania\sync;
@@ -702,6 +703,10 @@ public function validate()
{
$error[] = phpbb::$user->lang['NO_CATEGORY_NAME'];
}
+ else if (unicode::contains_unsupported($this->category_name))
+ {
+ $error[] = phpbb::$user->lang['CATEGORY_EMOJI_NOT_ALLOWED'];
+ }
if (!$this->category_name_clean || !preg_match('/^[a-zA-Z0-9\-\_]+$/', $this->category_name_clean))
{
diff --git a/includes/objects/contribution.php b/includes/objects/contribution.php
index 47d45a10e..fd7c26e0f 100644
--- a/includes/objects/contribution.php
+++ b/includes/objects/contribution.php
@@ -15,6 +15,7 @@
use phpbb\titania\composer\repository;
use phpbb\titania\contribution\type\collection as type_collection;
use phpbb\titania\contribution\type\type_interface;
+use phpbb\titania\unicode;
use phpbb\titania\ext;
use phpbb\titania\message\message;
use phpbb\titania\url\url;
@@ -1446,7 +1447,6 @@ public function change_status($new_status)
public function change_permalink($new_permalink)
{
$old_permalink = $this->contrib_name_clean;
- $new_permalink = url::generate_slug($new_permalink);
if ($this->validate_permalink($new_permalink, $old_permalink))
{
@@ -1555,10 +1555,11 @@ public function change_permalink($new_permalink)
array('active_coauthors' => array(username => username)).
* @param array $custom_fields Custom field values.
* @param string $old_permalink Old permalink. Defaults to empty string.
+ * @param string|null $new_permalink Submitted permalink. Defaults to the value on the entity.
*
* @return array Returns array containing any errors found.
*/
- public function validate($contrib_categories, $authors, $custom_fields, $old_permalink = '')
+ public function validate($contrib_categories, $authors, $custom_fields, $old_permalink = '', $new_permalink = null)
{
phpbb::$user->add_lang('ucp');
@@ -1569,6 +1570,32 @@ public function validate($contrib_categories, $authors, $custom_fields, $old_per
$error[] = phpbb::$user->lang['EMPTY_CONTRIB_NAME'];
}
+ $metadata = array(
+ $this->contrib_name,
+ $new_permalink !== null ? $new_permalink : $this->contrib_name_clean,
+ );
+ $metadata = array_merge($metadata, $custom_fields);
+
+ $demos = json_decode($this->contrib_demo, true);
+ if (is_array($demos))
+ {
+ // JSON encoding can otherwise hide unsupported characters behind
+ // surrogate escapes.
+ $metadata['contrib_demo'] = implode("\n", $demos);
+ }
+
+ $metadata_has_unsupported = false;
+
+ foreach ($metadata as $value)
+ {
+ if (is_string($value) && unicode::contains_unsupported($value, false))
+ {
+ $metadata_has_unsupported = true;
+ $error[] = phpbb::$user->lang['CONTRIB_EMOJI_NOT_ALLOWED'];
+ break;
+ }
+ }
+
if (!$this->contrib_type)
{
$error[] = phpbb::$user->lang['EMPTY_CONTRIB_TYPE'];
@@ -1602,13 +1629,8 @@ public function validate($contrib_categories, $authors, $custom_fields, $old_per
$this->set_type($this->contrib_type);
$error = array_merge($error, $this->type->validate_contrib_fields($custom_fields));
- if (!$this->contrib_name_clean)
- {
- // If they leave it blank automatically create it
- $this->generate_permalink();
- }
-
- if (($permalink_error = $this->validate_permalink($this->contrib_name_clean, $old_permalink)) !== false)
+ $permalink = $new_permalink !== null ? $new_permalink : $this->contrib_name_clean;
+ if (!$metadata_has_unsupported && ($permalink_error = $this->validate_permalink($permalink, $old_permalink)) !== false)
{
$error[] = $permalink_error;
}
@@ -1703,7 +1725,17 @@ public function validate($contrib_categories, $authors, $custom_fields, $old_per
*/
public function generate_permalink()
{
- $clean_name = url::generate_slug($this->contrib_name);
+ $this->contrib_name_clean = $this->get_generated_permalink();
+ }
+
+ /**
+ * Generate an available contribution permalink.
+ *
+ * @return string
+ */
+ public function get_generated_permalink()
+ {
+ $clean_name = $this->generate_permalink_slug($this->contrib_name);
$append = '';
$i = 2;
while ($this->permalink_exists($clean_name . $append))
@@ -1711,7 +1743,27 @@ public function generate_permalink()
$append = '_' . $i;
$i++;
}
- $this->contrib_name_clean = $clean_name . $append;
+
+ return $clean_name . $append;
+ }
+
+ /**
+ * Generate a contribution permalink containing Unicode letters, combining
+ * marks, numbers, and underscores.
+ *
+ * @param string $value
+ * @return string
+ */
+ protected function generate_permalink_slug($value)
+ {
+ // Variation selectors control the presentation of the preceding
+ // character. They are combining marks, but have no place in a
+ // permalink and can otherwise survive after an emoji is removed.
+ $value = preg_replace('/[\x{FE00}-\x{FE0F}\x{E0100}-\x{E01EF}]/u', '', $value);
+
+ // Preserve repeated and surrounding underscores for compatibility with
+ // existing permalink rules.
+ return preg_replace('/[^\p{L}\p{M}\p{N}_]+/u', '_', url::generate_slug($value));
}
/*
@@ -1724,14 +1776,26 @@ public function generate_permalink()
*/
public function validate_permalink($permalink, $old_permalink)
{
- if (url::generate_slug($permalink) !== $permalink)
+ // Preserve existing Unicode permalinks until they are intentionally changed.
+ if ($permalink !== '' && $permalink === $old_permalink)
+ {
+ return false;
+ }
+
+ $generated_permalink = $this->generate_permalink_slug($permalink);
+ if ($generated_permalink !== $permalink)
+ {
+ return phpbb::$user->lang('INVALID_PERMALINK', $generated_permalink);
+ }
+
+ if ($permalink === '')
{
- return phpbb::$user->lang('INVALID_PERMALINK', url::generate_slug($permalink));
+ return phpbb::$user->lang['EMPTY_CONTRIB_PERMALINK'];
}
- if ($permalink === '' || ($permalink !== $old_permalink && $this->permalink_exists($permalink)))
+ if ($permalink !== $old_permalink && $this->permalink_exists($permalink))
{
- return phpbb::$user->lang['CONTRIB_NAME_EXISTS'];
+ return phpbb::$user->lang['CONTRIB_PERMALINK_EXISTS'];
}
return false;
@@ -1748,7 +1812,8 @@ public function permalink_exists($permalink)
$sql = 'SELECT contrib_id
FROM ' . $this->sql_table . "
WHERE contrib_name_clean = '" . phpbb::$db->sql_escape($permalink) . "'
- AND contrib_type = " . (int) $this->contrib_type;
+ AND contrib_type = " . (int) $this->contrib_type . '
+ AND contrib_id <> ' . (int) $this->contrib_id;
$result = phpbb::$db->sql_query($sql);
$contrib_id = phpbb::$db->sql_fetchfield('contrib_id');
phpbb::$db->sql_freeresult($result);
diff --git a/includes/objects/faq.php b/includes/objects/faq.php
index 1a18939c3..3fcd954a4 100644
--- a/includes/objects/faq.php
+++ b/includes/objects/faq.php
@@ -62,7 +62,7 @@ public function __construct($faq_id = false)
$this->object_config = array_merge($this->object_config, array(
'faq_id' => array('default' => 0),
'contrib_id' => array('default' => 0),
- 'faq_subject' => array('default' => '', 'message_field' => 'subject', 'max' => 255),
+ 'faq_subject' => array('default' => '', 'message_field' => 'subject', 'max' => 255, 'encode_ucr' => true),
'faq_text' => array('default' => '', 'message_field' => 'message'),
'faq_text_bitfield' => array('default' => '', 'message_field' => 'message_bitfield'),
'faq_text_uid' => array('default' => '', 'message_field' => 'message_uid'),
diff --git a/includes/objects/post.php b/includes/objects/post.php
index a5dbc8391..15a0b1742 100644
--- a/includes/objects/post.php
+++ b/includes/objects/post.php
@@ -108,10 +108,10 @@ public function __construct($type = ext::TITANIA_SUPPORT, $topic = false, $post_
'post_edit_time' => array('default' => 0), // The last time that user edit the post
'post_edit_user' => array('default' => 0), // The last user to edit the post
- 'post_edit_reason' => array('default' => ''), // Reason for deleting/editing
+ 'post_edit_reason' => array('default' => '', 'max' => 255, 'encode_ucr' => true), // Reason for deleting/editing
'post_delete_user' => array('default' => 0), // The last user to delete the post
- 'post_subject' => array('default' => '', 'message_field' => 'subject', 'max' => 255),
+ 'post_subject' => array('default' => '', 'message_field' => 'subject', 'max' => 255, 'encode_ucr' => true),
'post_text' => array('default' => '', 'message_field' => 'message'),
'post_text_bitfield' => array('default' => '', 'message_field' => 'message_bitfield'),
'post_text_uid' => array('default' => '', 'message_field' => 'message_uid'),
diff --git a/includes/objects/queue.php b/includes/objects/queue.php
index 89241ea22..01371ea7a 100644
--- a/includes/objects/queue.php
+++ b/includes/objects/queue.php
@@ -216,13 +216,13 @@ public function update_first_queue_post($post_subject = false)
phpbb::$config['min_post_chars'] = 1;
phpbb::$config['max_post_chars'] = 0;
- $this->forum_queue_update_first_queue_post($post);
-
// Store the post
$post->generate_text_for_storage(true, true, true);
$post->submit();
$this->queue_topic_id = $post->topic_id;
+
+ $this->forum_queue_update_first_queue_post($post);
}
/**
@@ -826,8 +826,6 @@ protected function forum_queue_update_first_queue_post(&$post_object)
return;
}
- $post_object->submit();
-
titania::_include('functions_posting', 'phpbb_posting');
// Need some stuff
@@ -874,10 +872,11 @@ protected function forum_queue_update_first_queue_post(&$post_object)
get_formatted_filesize($download['filesize'])
);
- $post_text .= "\n\n" . $post_object->post_text;
+ $queue_post_text = $post_object->post_text;
+ handle_queue_attachments($post_object, $queue_post_text);
+ message::decode($queue_post_text, $post_object->post_text_uid);
- handle_queue_attachments($post_object, $post_text);
- message::decode($post_text, $post_object->post_text_uid);
+ $post_text .= "\n\n" . $queue_post_text;
$post_text .= "\n\n" . $path_helper->strip_url_params($post_object->get_url(), 'sid');
diff --git a/includes/objects/topic.php b/includes/objects/topic.php
index 208e091fc..ef0985d8a 100644
--- a/includes/objects/topic.php
+++ b/includes/objects/topic.php
@@ -89,8 +89,8 @@ public function __construct($topic_id = 0)
'topic_posts' => array('default' => ''), // Post count; separated by : between access levels ('10:9:8' = 10 team; 9 Mod Author; 8 Public)
'topic_views' => array('default' => 0), // View count
- 'topic_subject' => array('default' => ''),
- 'topic_subject_clean' => array('default' => ''),
+ 'topic_subject' => array('default' => '', 'max' => 255, 'encode_ucr' => true),
+ 'topic_subject_clean' => array('default' => '', 'max' => 255, 'encode_ucr' => true),
'topic_first_post_id' => array('default' => 0),
'topic_first_post_user_id' => array('default' => 0),
@@ -103,7 +103,7 @@ public function __construct($topic_id = 0)
'topic_last_post_username' => array('default' => ''),
'topic_last_post_user_colour' => array('default' => ''),
'topic_last_post_time' => array('default' => (int) titania::$time),
- 'topic_last_post_subject' => array('default' => ''),
+ 'topic_last_post_subject' => array('default' => '', 'max' => 255, 'encode_ucr' => true),
'phpbb_topic_id' => array('default' => 0),
));
diff --git a/language/en/common.php b/language/en/common.php
index e1a985e3b..751cd1c68 100644
--- a/language/en/common.php
+++ b/language/en/common.php
@@ -63,6 +63,7 @@
'CATEGORY_DELETED' => 'Category Deleted',
'CATEGORY_DESC' => 'Category Description',
'CATEGORY_DUPLICATE_PARENT' => 'Category cannot be its own parent.',
+ 'CATEGORY_EMOJI_NOT_ALLOWED'=> 'Four-byte characters are not allowed in category names.',
'CATEGORY_HAS_CHILDREN' => 'This category cannot be deleted because it contains children categories.',
'CATEGORY_INFORMATION' => 'Category Information',
'CATEGORY_NAME' => 'Category Name',
diff --git a/language/en/contributions.php b/language/en/contributions.php
index 35c9bdca8..3de7f78fc 100644
--- a/language/en/contributions.php
+++ b/language/en/contributions.php
@@ -72,19 +72,20 @@
'CONTRIB_DISABLED' => 'Hidden + Disabled',
'CONTRIB_DOWNLOAD_DISABLED' => 'Downloads Disabled',
'CONTRIB_EDITED' => 'The contribution has been successfully edited.',
+ 'CONTRIB_EMOJI_NOT_ALLOWED' => 'Four-byte characters are not allowed in contribution names, permalinks, authors, links, categories, or other metadata. They may be used in descriptions, posts, topics, comments, reports, and validation output.',
'CONTRIB_HIDDEN' => 'Hidden',
'CONTRIB_ISO_CODE' => 'ISO Code',
'CONTRIB_ISO_CODE_EXPLAIN' => 'The ISO code according to the Translation Coding Guidelines.',
'CONTRIB_LOCAL_NAME' => 'Local name',
'CONTRIB_LOCAL_NAME_EXPLAIN' => 'The localized name of the language, e.g. Français.',
'CONTRIB_NAME' => 'Contribution Name',
- 'CONTRIB_NAME_EXISTS' => 'The unique name has already been reserved.',
'CONTRIB_NEW' => 'New',
'CONTRIB_NONACTIVE_AUTHORS' => 'Non-Active Co-Authors (Past Contributors)',
'CONTRIB_NONACTIVE_AUTHORS_EXPLAIN' => 'Non-Active Co-Authors can not manage anything for the contribution and are only listed as previous authors.',
'CONTRIB_NOT_FOUND' => 'The contribution you requested could not be found.',
'CONTRIB_OWNER_UPDATED' => 'The owner has been changed.',
'CONTRIB_PERMALINK' => 'Contribution Permalink',
+ 'CONTRIB_PERMALINK_EXISTS' => 'That contribution permalink is already in use.',
'CONTRIB_PERMALINK_EXPLAIN' => 'Cleaned version of the contribution name, used to build the url for the contribution.
Leave blank to have one automatically created based on the contribution name.',
'CONTRIB_RELEASE_DATE' => 'Release date',
'CONTRIB_STATUS' => 'Contribution status',
@@ -118,7 +119,7 @@
'EMPTY_CONTRIB_ISO_CODE' => 'Enter the ISO Code',
'EMPTY_CONTRIB_LOCAL_NAME' => 'Enter the local name',
'EMPTY_CONTRIB_NAME' => 'Enter the contrib name',
- 'EMPTY_CONTRIB_PERMALINK' => 'Enter your proposal for permalink for the contribution',
+ 'EMPTY_CONTRIB_PERMALINK' => 'A valid contribution permalink could not be generated. Please enter one manually.',
'EMPTY_CONTRIB_TYPE' => 'Select at least one contribution type',
'ERROR_CONTRIB_EMAIL_FRIEND' => 'You are not permitted to recommend this contribution to someone else.',
@@ -185,6 +186,7 @@
'REVISION' => 'Revision',
'REVISIONS' => 'Revisions',
'REVISION_APPROVED' => 'Approved',
+ 'REVISION_EMOJI_NOT_ALLOWED' => 'Four-byte characters are not allowed in revision names, versions, licenses, or other revision metadata.',
'REVISION_DENIED' => 'Denied',
'REVISION_FOR_NEXT_PHPBB' => 'This revision has been submitted for the next phpBB release.',
'REVISION_IN_QUEUE' => 'You already have a revision in the validation queue. You must wait until the previous revision is approved or denied to submit a new one.',
diff --git a/manage/tool/composer/rebuild_repo.php b/manage/tool/composer/rebuild_repo.php
index d27f82d2f..b046e9c4e 100644
--- a/manage/tool/composer/rebuild_repo.php
+++ b/manage/tool/composer/rebuild_repo.php
@@ -19,6 +19,7 @@
use phpbb\titania\config\config as ext_config;
use phpbb\titania\contribution\type\collection as type_collection;
use phpbb\titania\controller\helper;
+use phpbb\titania\unicode;
use phpbb\titania\entity\package;
use phpbb\titania\ext;
use phpbb\titania\manage\tool\base;
@@ -357,6 +358,7 @@ protected function get_composer_json($file)
if ($path !== null)
{
$composer_json = file_get_contents($this->package->get_temp_path() . '/' . $path . '/composer.json');
+ $composer_json = unicode::escape_json($composer_json);
}
$this->package->cleanup();
diff --git a/posting.php b/posting.php
index 0525b4094..2c5f32988 100644
--- a/posting.php
+++ b/posting.php
@@ -809,10 +809,12 @@ public function split_topic($topic_id, $mode)
// Use new subject as the first post's subject to avoid issues when it gets approved
if (!$first_post['post_approved'])
{
- $sql = 'UPDATE ' . TITANIA_POSTS_TABLE . '
- SET post_subject = "' . $this->db->sql_escape($subject) . '"
- WHERE post_id = ' . (int) $first_post['post_id'];
- $this->db->sql_query($sql);
+ $first_post_object = new \titania_post($topic->topic_type, $topic, $first_post['post_id']);
+ if ($first_post_object->load())
+ {
+ $first_post_object->post_subject = $subject;
+ $first_post_object->update();
+ }
}
}
}
diff --git a/unicode.php b/unicode.php
new file mode 100644
index 000000000..e984dfe26
--- /dev/null
+++ b/unicode.php
@@ -0,0 +1,83 @@
+
+ * @license GNU General Public License, version 2 (GPL-2.0)
+ *
+ * For full copyright and license information, please see
+ * the docs/CREDITS.txt file.
+ *
+ */
+
+namespace phpbb\titania;
+
+class unicode
+{
+ /**
+ * Escape four-byte UTF-8 characters as JSON surrogate pairs.
+ *
+ * This leaves the structure and formatting of the JSON document intact.
+ *
+ * @param string $json
+ * @return string
+ */
+ public static function escape_json($json)
+ {
+ $escaped_json = preg_replace_callback(
+ '/[\x{10000}-\x{10FFFF}]/u',
+ function ($matches)
+ {
+ $codepoint = utf8_ord($matches[0]) - 0x10000;
+ $high_surrogate = 0xD800 + ($codepoint >> 10);
+ $low_surrogate = 0xDC00 + ($codepoint & 0x3FF);
+
+ return sprintf('\\u%04X\\u%04X', $high_surrogate, $low_surrogate);
+ },
+ $json
+ );
+
+ if ($escaped_json === null)
+ {
+ throw new \UnexpectedValueException('JSON contains invalid UTF-8.');
+ }
+
+ return $escaped_json;
+ }
+
+ /**
+ * Check whether a string contains a four-byte character unsupported by
+ * phpBB's MySQL utf8 schema.
+ *
+ * @param string $value
+ * @param bool $decode_ncr Whether to decode numeric character references.
+ * @return bool
+ */
+ public static function contains_unsupported($value, $decode_ncr = true)
+ {
+ if ($decode_ncr)
+ {
+ $value = utf8_decode_ncr($value);
+ }
+
+ return (bool) preg_match('/[\x{10000}-\x{10FFFF}]/u', $value);
+ }
+
+ /**
+ * Remove four-byte characters unsupported by phpBB's MySQL utf8 schema.
+ *
+ * @param string $value
+ * @return string
+ */
+ public static function strip_unsupported($value)
+ {
+ $value = utf8_decode_ncr($value);
+
+ return preg_replace(
+ '/[\x{10000}-\x{10FFFF}]/u',
+ ' ',
+ $value
+ );
+ }
+}
diff --git a/url/url.php b/url/url.php
index df8fbac38..55811a400 100644
--- a/url/url.php
+++ b/url/url.php
@@ -192,6 +192,7 @@ public function remove_nth_param($nth)
*/
public static function generate_slug($string)
{
+ $string = \phpbb\titania\unicode::strip_unsupported($string);
$string = self::url_replace($string, false);
// Replace any number of spaces with a single underscore