From 0379eb61b7b0c7c4a375e2fd754ae9ac01972f7a Mon Sep 17 00:00:00 2001 From: ECYaz Date: Mon, 27 Jul 2026 09:08:22 -0400 Subject: [PATCH 1/2] Allow emoji in contribution names A four byte character in a contribution name reaches the utf8 column as-is and MySQL rejects the row, both when the contribution is created and when it is edited. Replace those characters with their numeric character reference in submit(), using the same utf8_encode_ucr() core applies to report text. Encoding in the entity rather than at the call sites keeps the stored form out of everything that reads the name, including the release topic subject, the feed and the generated permalink, and no consumer needs to decode it. Character references are dropped when generating a permalink from the name, so their digits do not end up in the URL. --- includes/objects/contribution.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/includes/objects/contribution.php b/includes/objects/contribution.php index 9818f7db0..79f2a9e67 100644 --- a/includes/objects/contribution.php +++ b/includes/objects/contribution.php @@ -1091,6 +1091,13 @@ public function set_demo_url($branch, $url) */ public function submit() { + // Emojis and other four byte characters are not allowed by MySQL in the + // utf8 column the name is stored in, so replace them with their numeric + // character reference, as core does for report text. Doing it here keeps + // the encoded form out of everything that reads the name, including the + // release topic subject and the generated slug. + $this->contrib_name = utf8_encode_ucr($this->contrib_name); + if (!$this->contrib_id) { // Make sure the author exists, if not we create one (do this before returning if not approved...else we need to duplicate this code in a bunch of places) @@ -1633,7 +1640,9 @@ public function validate($contrib_categories, $authors, $custom_fields, $old_per */ public function generate_permalink() { - $clean_name = url::generate_slug($this->contrib_name); + // Drop any character references left by utf8_encode_ucr() rather than + // letting their digits end up in the permalink. + $clean_name = url::generate_slug(preg_replace('/&#[0-9]+;/', '', $this->contrib_name)); $append = ''; $i = 2; while ($this->permalink_exists($clean_name . $append)) From 195e74a21e7a2857178bcb7c74ea55982786bbe0 Mon Sep 17 00:00:00 2001 From: ECYaz Date: Mon, 27 Jul 2026 10:25:31 -0400 Subject: [PATCH 2/2] Keep four byte characters out of generated slugs The permalink is generated during validation, before the name is encoded, so a name containing an emoji still reached contrib_name_clean as-is and MySQL rejected the row: Incorrect string value: '\xF0\x9F\x86\x95_s...' for column 'contrib_name_clean' at row 1 [1366] Drop character references and characters outside the basic multilingual plane in generate_slug() rather than in generate_permalink(). A slug has to be safe for a URL and storable in a utf8 column whichever way round it is called, and every caller benefits, including the topic subject slug. The strip added to generate_permalink() is no longer needed and is removed. --- includes/objects/contribution.php | 4 +--- url/url.php | 7 +++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/includes/objects/contribution.php b/includes/objects/contribution.php index 79f2a9e67..15a41ac63 100644 --- a/includes/objects/contribution.php +++ b/includes/objects/contribution.php @@ -1640,9 +1640,7 @@ public function validate($contrib_categories, $authors, $custom_fields, $old_per */ public function generate_permalink() { - // Drop any character references left by utf8_encode_ucr() rather than - // letting their digits end up in the permalink. - $clean_name = url::generate_slug(preg_replace('/&#[0-9]+;/', '', $this->contrib_name)); + $clean_name = url::generate_slug($this->contrib_name); $append = ''; $i = 2; while ($this->permalink_exists($clean_name . $append)) diff --git a/url/url.php b/url/url.php index df8fbac38..0b7b995ec 100644 --- a/url/url.php +++ b/url/url.php @@ -192,6 +192,13 @@ public function remove_nth_param($nth) */ public static function generate_slug($string) { + // Neither a character reference nor a character outside the basic + // multilingual plane belongs in a slug: the columns slugs are stored in + // are utf8, which cannot hold the latter, and the digits of the former + // would end up in the URL. + $string = preg_replace('/&#[0-9]+;/', '', $string); + $string = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $string); + $string = self::url_replace($string, false); // Replace any number of spaces with a single underscore