Index: /trunk/lib/FormValidator.inc.php
===================================================================
--- /trunk/lib/FormValidator.inc.php	(revision 721)
+++ /trunk/lib/FormValidator.inc.php	(revision 722)
@@ -67,4 +67,5 @@
         'notice' => ' sc-msg-notice ',
         'success' => ' sc-msg-success ',
+        'use_raise_msg' => false,
     );
 
@@ -151,4 +152,10 @@
     public function addError($form_name, $msg='', $type=MSG_ERR, $file=null, $line=null)
     {
+        $app =& App::getInstance();
+
+        if (true === $this->getParam('use_raise_msg')) {
+            $app->raiseMsg($msg, $type, $file, $line);
+        }
+
         $this->errors[] = array(
             'name' => $form_name,
Index: /trunk/lib/Utilities.inc.php
===================================================================
--- /trunk/lib/Utilities.inc.php	(revision 721)
+++ /trunk/lib/Utilities.inc.php	(revision 722)
@@ -520,21 +520,4 @@
 }
 
-/*
-* Converts a string into a URL-safe slug, removing spaces and non word characters.
-*
-* @access   public
-* @param    string  $str    String to convert.
-* @return   string          URL-safe slug.
-* @author   Quinn Comendant <quinn@strangecode.com>
-* @version  1.0
-* @since    18 Aug 2014 12:54:29
-*/
-function URLSlug($str)
-{
-    $slug = preg_replace(array('/\W+/u', '/^-+|-+$/u'), array('-', ''), $str);
-    $slug = strtolower($slug);
-    return $slug;
-}
-
 /**
  * Return a human readable disk space measurement. Input value measured in bytes.
@@ -623,6 +606,53 @@
 }
 
-/**
- * Removes non-latin characters from file name, using htmlentities to convert known weirdos into regular squares.
+/*
+* Converts strange characters into ASCII using a htmlentities hack. If a character does not have a specific rule, it will remain as its entity name, e.g., `5Â¢` becomes `5&cent;` which becomes `5cent`.
+*
+* @access   public
+* @param    string  $str    Input string of text containing accents.
+* @return   string          String with accented characters converted to ASCII equivalents.
+* @author   Quinn Comendant <quinn@strangecode.com>
+* @since    30 Apr 2020 21:29:16
+*/
+function simplifyAccents($str)
+{
+    $app =& App::getInstance();
+
+    return preg_replace([
+        '/&amp;(?=[\w\d#]{1,10};)/ui',
+        '/&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml|caron);/ui',
+        '/&(?:ndash|mdash|horbar);/ui',
+        '/&(?:nbsp);/ui',
+        '/&(?:bdquo|ldquo|ldquor|lsquo|lsquor|rdquo|rdquor|rsquo|rsquor|sbquo|lsaquo|rsaquo);/ui',
+        '/&(?:amp);/ui', // This replacement must come after matching all other entities.
+        '/[&;]+/u',
+    ], [
+        '&',
+        '$1',
+        '-',
+        ' ',
+        '',
+        'and',
+        '',
+    ], htmlentities($str, ENT_NOQUOTES | ENT_IGNORE, $app->getParam('character_set')));
+}
+
+/*
+* Converts a string into a URL-safe slug, removing spaces and non word characters.
+*
+* @access   public
+* @param    string  $str    String to convert.
+* @return   string          URL-safe slug.
+* @author   Quinn Comendant <quinn@strangecode.com>
+* @version  1.0
+* @since    18 Aug 2014 12:54:29
+*/
+function URLSlug($str)
+{
+    return strtolower(urlencode(preg_replace(['/[-\sââ.:;?!@#=+_\/\\\]+|(?:&nbsp;|&#160;|&ndash;|&#8211;|&mdash;|&#8212;|%c2%a0|%e2%80%93|%e2%80%9)+/u', '/-+/u', '/[^\w-]+/u', '/^-+|-+$/u'], ['-', '-', '', ''], simplifyAccents($str))));
+}
+
+/**
+ * Converts a string of text into a safe file name by removing non-ASCII characters and non-word characters.
  *
  * @access  public
@@ -634,17 +664,5 @@
     $app =& App::getInstance();
 
-    $file_name = preg_replace(array(
-        '/&([a-z]{1,2})(?:acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml|caron);/ui',
-        '/&(?:amp);/ui',
-        '/[&;]+/u',
-        '/[^a-zA-Z0-9()@._=+-]+/u',
-        '/^_+|_+$/u'
-    ), array(
-        '$1',
-        'and',
-        '',
-        '_',
-        ''
-    ), htmlentities($file_name, ENT_NOQUOTES | ENT_IGNORE, $app->getParam('character_set')));
+    $file_name = preg_replace(['/[^a-zA-Z0-9()@._=+-]+/u', '/^_+|_+$/u'], ['_', ''], simplifyAccents($file_name));
     return mb_substr($file_name, 0, 250);
 }
Index: /trunk/tests/UtilitiesTest.php
===================================================================
--- /trunk/tests/UtilitiesTest.php	(revision 721)
+++ /trunk/tests/UtilitiesTest.php	(revision 722)
@@ -283,3 +283,46 @@
         }
     }
+
+    function test_URLSlug()
+    {
+        $strings = [
+            'This becomes a slug' => 'this-becomes-a-slug',
+            'http://äŸå­.å·ç­çºž/?x=y&1=2#asdf' => 'http-%e4%be%8b%e5%ad%90-%e5%8d%b7%e7%ad%92%e7%ba%b8-x-yand1-2-asdf',
+            'Ã Ã€ Ã Ã¶ Ã ÃŒ Ã a Ã ÃŠ Ã Ãž Ã
+ Ã¥' => 'a-a-o-o-u-u-sz-a-ae-ae-o-o-a-a',
+            ' - ' => '',
+            '' => '',
+            ' ' => '',
+            '?' => '',
+            'a file.JPG' => 'a-file-jpg',
+            '/a/path/file.JPG' => 'a-path-file-jpg',
+            '/a/directory/' => 'a-directory',
+            '/' => '',
+            'this-is-already-a-slug' => 'this-is-already-a-slug',
+            'HTML entities&nbsp;ok&mdash;or not!' => 'html-entities-ok-or-not',
+            '<p>tagsâŠ</p>' => 'ltpgttagshelliplt-pgt',
+            'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.' => 'lorem-ipsum-dolor-sit-amet-consetetur-sadipscing-elitr-sed-diam-nonumy-eirmod-tempor-invidunt-ut-labore-et-dolore-magna-aliquyam-erat-sed-diam-voluptua-at-vero-eos-et-accusam-et-justo-duo-dolores-et-ea-rebum-stet-clita-kasd-gubergren-no-sea-takimata-sanctus-est-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-consetetur-sadipscing-elitr-sed-diam-nonumy-eirmod-tempor-invidunt-ut-labore-et-dolore-magna-aliquyam-erat-sed-diam-voluptua-at-vero-eos-et-accusam-et-justo-duo-dolores-et-ea-rebum-stet-clita-kasd-gubergren-no-sea-takimata-sanctus-est-lorem-ipsum-dolor-sit-amet-lorem-ipsum-dolor-sit-amet-consetetur-sadipscing-elitr-sed-diam-nonumy-eirmod-tempor-invidunt-ut-labore-et-dolore-magna-aliquyam-erat-sed-diam-voluptua-at-vero-eos-et-accusam-et-justo-duo-dolores-et-ea-rebum-stet-clita-kasd-gubergren-no-sea-takimata-sanctus-est-lorem-ipsum-dolor-sit-amet',
+            // Binary data.
+            'xÃâ¡-]ÃÂ¯pÄ±
+oË;ÃÂ¯)0Ã±]Ã 
+fhHÂ¡Ãg+Â£KËH
+Ën*âÃ
+â¡ÃËÃŒQÃÃµ.âulÃâ ËyÃËÃ¯uâ«
+âºÂ¢>kÆâÃŠÃ¬+Â¯âË âE
+Â¬Â±ÃhâUÃ·%>âï¬ï¬â€1
+' => 'xodagger-emacrp%c4%b1-otildecmacr0nafhhiexcleg-poundk%cb%87h-nedaggeritildeuqoo-lozulineyatildeiuintcentgtkfnofsumaei-macrinfin-enotplusmnahudividegtasymp%ef%ac%81%ef%ac%81le1',
+            '-- repeated - characters -- so---extra - - - look-out!' => 'repeated-characters-so-extra-look-out',
+            'NesthÃ€kchen\'s Teenage Years' => 'nesthakchens-teenage-years',
+            'áŒÏÎ±ÏÎœÎµá¿Ï AkharneÃ®s' => '%e1%bc%88chialpharhonuepsilon%e1%bf%96sigmaf-akharneis',
+            'ÐÐœÑÐŸÐœ ÐÐ°Ð²Ð»ÐŸÐ²ÐžÑ Ð§ÐµÑ
+ÐŸÐ²' => '%d0%90%d0%bd%d1%82%d0%be%d0%bd-%d0%9f%d0%b0%d0%b2%d0%bb%d0%be%d0%b2%d0%b8%d1%87-%d0%a7%d0%b5%d1%85%d0%be%d0%b2',
+            'Demain dÃšs l\'aube' => 'demain-des-laube',
+            'Demain dÃšs lâaube' => 'demain-des-laube',
+        ];
+        foreach ($strings as $input => $expected) {
+            // printf("%s\n", URLSlug($input));
+            $result = URLSlug($input);
+            $this->assertEquals($expected, $result, sprintf('Failed with input: %s', $input));
+        }
+    }
 }
