2023-01-11 20:00:01 +00:00
|
|
|
<?php
|
2023-10-15 19:06:31 +00:00
|
|
|
|
2023-03-10 20:20:17 +00:00
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Dotclear\Plugin\cleanURLs;
|
|
|
|
|
2023-10-15 19:06:31 +00:00
|
|
|
use Dotclear\Interface\Core\BlogInterface;
|
2023-04-23 12:52:40 +00:00
|
|
|
use Dotclear\Database\Cursor;
|
2023-04-20 19:42:56 +00:00
|
|
|
|
2023-01-11 20:16:35 +00:00
|
|
|
/**
|
2023-10-15 19:06:31 +00:00
|
|
|
* @brief cleanURLs main class.
|
|
|
|
* @ingroup cleanURLs
|
2023-01-11 20:16:35 +00:00
|
|
|
*
|
2023-10-15 19:06:31 +00:00
|
|
|
* @author Pierre Rudloff (author)
|
|
|
|
* @author Jean-Christian Denis (latest)
|
|
|
|
* @copyright GPL-3.0 https://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
*/
|
2023-01-11 20:00:01 +00:00
|
|
|
class CleanURLs
|
|
|
|
{
|
2023-10-15 19:06:31 +00:00
|
|
|
/**
|
|
|
|
* Chars to convert from.
|
|
|
|
*
|
|
|
|
* @var string CONVERT_FROM_CHARS
|
|
|
|
*/
|
2023-04-26 18:45:01 +00:00
|
|
|
public const CONVERT_FROM_CHARS = ',!.?;:@$«»°*';
|
|
|
|
|
2023-10-15 19:06:31 +00:00
|
|
|
/**
|
|
|
|
* Chars to convert to.
|
|
|
|
*
|
|
|
|
* @var string CONVERT_TO_CHAR
|
|
|
|
*/
|
|
|
|
public const CONVERT_TO_CHAR = '-';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List of special chars to convert.
|
|
|
|
*
|
|
|
|
* @var string $convert_from_chars
|
|
|
|
*/
|
2023-04-26 18:45:01 +00:00
|
|
|
public static string $convert_from_chars = self::CONVERT_FROM_CHARS;
|
|
|
|
|
2023-10-15 19:06:31 +00:00
|
|
|
/**
|
|
|
|
* A char to convert to.
|
|
|
|
*
|
|
|
|
* @var string $convert_to_char
|
|
|
|
*/
|
2023-04-26 18:45:01 +00:00
|
|
|
public static string $convert_to_char = self::CONVERT_TO_CHAR;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset to default values.
|
|
|
|
*/
|
|
|
|
public static function reset(): void
|
|
|
|
{
|
|
|
|
self::$convert_from_chars = self::CONVERT_FROM_CHARS;
|
|
|
|
self::$convert_to_char = self::CONVERT_TO_CHAR;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert specials chars to a another char.
|
|
|
|
*
|
|
|
|
* @param null|string $str The string
|
|
|
|
*
|
|
|
|
* @return null|string The converted string
|
|
|
|
*/
|
|
|
|
public static function charsToChar(?string $str): ?string
|
|
|
|
{
|
|
|
|
return is_null($str) || strlen(self::$convert_to_char) != 1 ? $str :
|
|
|
|
str_replace(str_split(self::$convert_from_chars), self::$convert_to_char, $str);
|
|
|
|
}
|
|
|
|
|
2023-01-11 20:00:01 +00:00
|
|
|
/**
|
|
|
|
* Check if a string is UTF-8
|
2023-01-11 20:16:35 +00:00
|
|
|
*
|
2023-01-11 20:00:01 +00:00
|
|
|
* @author WordPress
|
|
|
|
* @license https://wordpress.org/about/gpl/ GPLv2
|
2023-04-20 19:42:56 +00:00
|
|
|
*
|
|
|
|
* @param string $str The string to check
|
|
|
|
*
|
|
|
|
* @return bool
|
2023-01-11 20:00:01 +00:00
|
|
|
* */
|
2023-04-20 19:42:56 +00:00
|
|
|
public static function seemsUtf8(string $str): bool
|
2023-01-11 20:00:01 +00:00
|
|
|
{
|
|
|
|
$length = strlen($str);
|
2023-01-11 20:16:35 +00:00
|
|
|
for ($i = 0; $i < $length; $i++) {
|
2023-01-11 20:00:01 +00:00
|
|
|
$c = ord($str[$i]);
|
|
|
|
if ($c < 0x80) {
|
|
|
|
$n = 0; // 0bbbbbbb
|
|
|
|
} elseif (($c & 0xE0) == 0xC0) {
|
2023-01-11 20:16:35 +00:00
|
|
|
$n = 1; // 110bbbbb
|
2023-01-11 20:00:01 +00:00
|
|
|
} elseif (($c & 0xF0) == 0xE0) {
|
2023-01-11 20:16:35 +00:00
|
|
|
$n = 2; // 1110bbbb
|
|
|
|
} elseif (($c & 0xF8) == 0xF0) {
|
|
|
|
$n = 3; // 11110bbb
|
2023-01-11 20:00:01 +00:00
|
|
|
} elseif (($c & 0xFC) == 0xF8) {
|
2023-01-11 20:16:35 +00:00
|
|
|
$n = 4; // 111110bb
|
2023-01-11 20:00:01 +00:00
|
|
|
} elseif (($c & 0xFE) == 0xFC) {
|
2023-01-11 20:16:35 +00:00
|
|
|
$n = 5; // 1111110b
|
2023-01-11 20:00:01 +00:00
|
|
|
} else {
|
|
|
|
return false; // Does not match any model
|
|
|
|
}
|
2023-01-11 20:16:35 +00:00
|
|
|
for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
|
2023-01-11 20:00:01 +00:00
|
|
|
if ((++$i == $length) || ((ord($str[$i]) & 0xC0) != 0x80)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-01-11 20:16:35 +00:00
|
|
|
|
2023-01-11 20:00:01 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Converts all accent characters to ASCII characters.
|
|
|
|
*
|
|
|
|
* If there are no accent characters, then the string given is just returned.
|
|
|
|
*
|
|
|
|
* @author WordPress
|
|
|
|
* @license https://wordpress.org/about/gpl/ GPLv2
|
2023-04-20 19:42:56 +00:00
|
|
|
*
|
|
|
|
* @param string $string Text that might have accent characters
|
|
|
|
*
|
|
|
|
* @return string Filtered string with replaced "nice" characters.
|
2023-01-11 20:00:01 +00:00
|
|
|
*/
|
2023-04-20 19:42:56 +00:00
|
|
|
public static function removeAccents(string $string): string
|
2023-01-11 20:00:01 +00:00
|
|
|
{
|
|
|
|
if (!preg_match('/[\x80-\xff]/', $string)) {
|
|
|
|
return $string;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (self::seemsUtf8($string)) {
|
2023-01-11 20:16:35 +00:00
|
|
|
$chars = [
|
|
|
|
// Decompositions for Latin-1 Supplement
|
2023-03-10 20:20:17 +00:00
|
|
|
chr(195) . chr(128) => 'A', chr(195) . chr(129) => 'A',
|
|
|
|
chr(195) . chr(130) => 'A', chr(195) . chr(131) => 'A',
|
|
|
|
chr(195) . chr(132) => 'A', chr(195) . chr(133) => 'A',
|
|
|
|
chr(195) . chr(135) => 'C', chr(195) . chr(136) => 'E',
|
|
|
|
chr(195) . chr(137) => 'E', chr(195) . chr(138) => 'E',
|
|
|
|
chr(195) . chr(139) => 'E', chr(195) . chr(140) => 'I',
|
|
|
|
chr(195) . chr(141) => 'I', chr(195) . chr(142) => 'I',
|
|
|
|
chr(195) . chr(143) => 'I', chr(195) . chr(145) => 'N',
|
|
|
|
chr(195) . chr(146) => 'O', chr(195) . chr(147) => 'O',
|
|
|
|
chr(195) . chr(148) => 'O', chr(195) . chr(149) => 'O',
|
|
|
|
chr(195) . chr(150) => 'O', chr(195) . chr(153) => 'U',
|
|
|
|
chr(195) . chr(154) => 'U', chr(195) . chr(155) => 'U',
|
|
|
|
chr(195) . chr(156) => 'U', chr(195) . chr(157) => 'Y',
|
|
|
|
chr(195) . chr(159) => 's', chr(195) . chr(160) => 'a',
|
|
|
|
chr(195) . chr(161) => 'a', chr(195) . chr(162) => 'a',
|
|
|
|
chr(195) . chr(163) => 'a', chr(195) . chr(164) => 'a',
|
|
|
|
chr(195) . chr(165) => 'a', chr(195) . chr(167) => 'c',
|
|
|
|
chr(195) . chr(168) => 'e', chr(195) . chr(169) => 'e',
|
|
|
|
chr(195) . chr(170) => 'e', chr(195) . chr(171) => 'e',
|
|
|
|
chr(195) . chr(172) => 'i', chr(195) . chr(173) => 'i',
|
|
|
|
chr(195) . chr(174) => 'i', chr(195) . chr(175) => 'i',
|
|
|
|
chr(195) . chr(177) => 'n', chr(195) . chr(178) => 'o',
|
|
|
|
chr(195) . chr(179) => 'o', chr(195) . chr(180) => 'o',
|
|
|
|
chr(195) . chr(181) => 'o', chr(195) . chr(182) => 'o',
|
|
|
|
chr(195) . chr(182) => 'o', chr(195) . chr(185) => 'u',
|
|
|
|
chr(195) . chr(186) => 'u', chr(195) . chr(187) => 'u',
|
|
|
|
chr(195) . chr(188) => 'u', chr(195) . chr(189) => 'y',
|
|
|
|
chr(195) . chr(191) => 'y',
|
2023-01-11 20:16:35 +00:00
|
|
|
// Decompositions for Latin Extended-A
|
2023-03-10 20:20:17 +00:00
|
|
|
chr(196) . chr(128) => 'A', chr(196) . chr(129) => 'a',
|
|
|
|
chr(196) . chr(130) => 'A', chr(196) . chr(131) => 'a',
|
|
|
|
chr(196) . chr(132) => 'A', chr(196) . chr(133) => 'a',
|
|
|
|
chr(196) . chr(134) => 'C', chr(196) . chr(135) => 'c',
|
|
|
|
chr(196) . chr(136) => 'C', chr(196) . chr(137) => 'c',
|
|
|
|
chr(196) . chr(138) => 'C', chr(196) . chr(139) => 'c',
|
|
|
|
chr(196) . chr(140) => 'C', chr(196) . chr(141) => 'c',
|
|
|
|
chr(196) . chr(142) => 'D', chr(196) . chr(143) => 'd',
|
|
|
|
chr(196) . chr(144) => 'D', chr(196) . chr(145) => 'd',
|
|
|
|
chr(196) . chr(146) => 'E', chr(196) . chr(147) => 'e',
|
|
|
|
chr(196) . chr(148) => 'E', chr(196) . chr(149) => 'e',
|
|
|
|
chr(196) . chr(150) => 'E', chr(196) . chr(151) => 'e',
|
|
|
|
chr(196) . chr(152) => 'E', chr(196) . chr(153) => 'e',
|
|
|
|
chr(196) . chr(154) => 'E', chr(196) . chr(155) => 'e',
|
|
|
|
chr(196) . chr(156) => 'G', chr(196) . chr(157) => 'g',
|
|
|
|
chr(196) . chr(158) => 'G', chr(196) . chr(159) => 'g',
|
|
|
|
chr(196) . chr(160) => 'G', chr(196) . chr(161) => 'g',
|
|
|
|
chr(196) . chr(162) => 'G', chr(196) . chr(163) => 'g',
|
|
|
|
chr(196) . chr(164) => 'H', chr(196) . chr(165) => 'h',
|
|
|
|
chr(196) . chr(166) => 'H', chr(196) . chr(167) => 'h',
|
|
|
|
chr(196) . chr(168) => 'I', chr(196) . chr(169) => 'i',
|
|
|
|
chr(196) . chr(170) => 'I', chr(196) . chr(171) => 'i',
|
|
|
|
chr(196) . chr(172) => 'I', chr(196) . chr(173) => 'i',
|
|
|
|
chr(196) . chr(174) => 'I', chr(196) . chr(175) => 'i',
|
|
|
|
chr(196) . chr(176) => 'I', chr(196) . chr(177) => 'i',
|
|
|
|
chr(196) . chr(178) => 'IJ',chr(196) . chr(179) => 'ij',
|
|
|
|
chr(196) . chr(180) => 'J', chr(196) . chr(181) => 'j',
|
|
|
|
chr(196) . chr(182) => 'K', chr(196) . chr(183) => 'k',
|
|
|
|
chr(196) . chr(184) => 'k', chr(196) . chr(185) => 'L',
|
|
|
|
chr(196) . chr(186) => 'l', chr(196) . chr(187) => 'L',
|
|
|
|
chr(196) . chr(188) => 'l', chr(196) . chr(189) => 'L',
|
|
|
|
chr(196) . chr(190) => 'l', chr(196) . chr(191) => 'L',
|
|
|
|
chr(197) . chr(128) => 'l', chr(197) . chr(129) => 'L',
|
|
|
|
chr(197) . chr(130) => 'l', chr(197) . chr(131) => 'N',
|
|
|
|
chr(197) . chr(132) => 'n', chr(197) . chr(133) => 'N',
|
|
|
|
chr(197) . chr(134) => 'n', chr(197) . chr(135) => 'N',
|
|
|
|
chr(197) . chr(136) => 'n', chr(197) . chr(137) => 'N',
|
|
|
|
chr(197) . chr(138) => 'n', chr(197) . chr(139) => 'N',
|
|
|
|
chr(197) . chr(140) => 'O', chr(197) . chr(141) => 'o',
|
|
|
|
chr(197) . chr(142) => 'O', chr(197) . chr(143) => 'o',
|
|
|
|
chr(197) . chr(144) => 'O', chr(197) . chr(145) => 'o',
|
|
|
|
chr(197) . chr(146) => 'OE',chr(197) . chr(147) => 'oe',
|
|
|
|
chr(197) . chr(148) => 'R',chr(197) . chr(149) => 'r',
|
|
|
|
chr(197) . chr(150) => 'R',chr(197) . chr(151) => 'r',
|
|
|
|
chr(197) . chr(152) => 'R',chr(197) . chr(153) => 'r',
|
|
|
|
chr(197) . chr(154) => 'S',chr(197) . chr(155) => 's',
|
|
|
|
chr(197) . chr(156) => 'S',chr(197) . chr(157) => 's',
|
|
|
|
chr(197) . chr(158) => 'S',chr(197) . chr(159) => 's',
|
|
|
|
chr(197) . chr(160) => 'S', chr(197) . chr(161) => 's',
|
|
|
|
chr(197) . chr(162) => 'T', chr(197) . chr(163) => 't',
|
|
|
|
chr(197) . chr(164) => 'T', chr(197) . chr(165) => 't',
|
|
|
|
chr(197) . chr(166) => 'T', chr(197) . chr(167) => 't',
|
|
|
|
chr(197) . chr(168) => 'U', chr(197) . chr(169) => 'u',
|
|
|
|
chr(197) . chr(170) => 'U', chr(197) . chr(171) => 'u',
|
|
|
|
chr(197) . chr(172) => 'U', chr(197) . chr(173) => 'u',
|
|
|
|
chr(197) . chr(174) => 'U', chr(197) . chr(175) => 'u',
|
|
|
|
chr(197) . chr(176) => 'U', chr(197) . chr(177) => 'u',
|
|
|
|
chr(197) . chr(178) => 'U', chr(197) . chr(179) => 'u',
|
|
|
|
chr(197) . chr(180) => 'W', chr(197) . chr(181) => 'w',
|
|
|
|
chr(197) . chr(182) => 'Y', chr(197) . chr(183) => 'y',
|
|
|
|
chr(197) . chr(184) => 'Y', chr(197) . chr(185) => 'Z',
|
|
|
|
chr(197) . chr(186) => 'z', chr(197) . chr(187) => 'Z',
|
|
|
|
chr(197) . chr(188) => 'z', chr(197) . chr(189) => 'Z',
|
|
|
|
chr(197) . chr(190) => 'z', chr(197) . chr(191) => 's',
|
2023-01-11 20:16:35 +00:00
|
|
|
// Euro Sign
|
|
|
|
chr(226) . chr(130) . chr(172) => 'E',
|
|
|
|
// GBP (Pound) Sign
|
2023-03-10 20:20:17 +00:00
|
|
|
chr(194) . chr(163) => ''];
|
2023-01-11 20:00:01 +00:00
|
|
|
|
|
|
|
$string = strtr($string, $chars);
|
|
|
|
} else {
|
|
|
|
// Assume ISO-8859-1 if not UTF-8
|
2023-01-11 20:16:35 +00:00
|
|
|
$chars['in'] = chr(128) . chr(131) . chr(138) . chr(142) . chr(154) . chr(158)
|
|
|
|
. chr(159) . chr(162) . chr(165) . chr(181) . chr(192) . chr(193) . chr(194)
|
|
|
|
. chr(195) . chr(196) . chr(197) . chr(199) . chr(200) . chr(201) . chr(202)
|
|
|
|
. chr(203) . chr(204) . chr(205) . chr(206) . chr(207) . chr(209) . chr(210)
|
|
|
|
. chr(211) . chr(212) . chr(213) . chr(214) . chr(216) . chr(217) . chr(218)
|
|
|
|
. chr(219) . chr(220) . chr(221) . chr(224) . chr(225) . chr(226) . chr(227)
|
|
|
|
. chr(228) . chr(229) . chr(231) . chr(232) . chr(233) . chr(234) . chr(235)
|
|
|
|
. chr(236) . chr(237) . chr(238) . chr(239) . chr(241) . chr(242) . chr(243)
|
|
|
|
. chr(244) . chr(245) . chr(246) . chr(248) . chr(249) . chr(250) . chr(251)
|
|
|
|
. chr(252) . chr(253) . chr(255);
|
2023-01-11 20:00:01 +00:00
|
|
|
|
2023-01-11 20:16:35 +00:00
|
|
|
$chars['out'] = 'EfSZszYcYuAAAAAACEEEEIIIINOOOOOOUUUUY' .
|
|
|
|
'aaaaaaceeeeiiiinoooooouuuuyy';
|
2023-01-11 20:00:01 +00:00
|
|
|
|
2023-01-11 20:16:35 +00:00
|
|
|
$string = strtr($string, $chars['in'], $chars['out']);
|
|
|
|
$double_chars['in'] = [
|
2023-01-11 20:00:01 +00:00
|
|
|
chr(140), chr(156), chr(198), chr(208), chr(222),
|
2023-01-11 20:16:35 +00:00
|
|
|
chr(223), chr(230), chr(240), chr(254),
|
|
|
|
];
|
|
|
|
$double_chars['out'] = [
|
|
|
|
'OE', 'oe', 'AE', 'DH', 'TH', 'ss', 'ae', 'dh', 'th',
|
|
|
|
];
|
2023-01-11 20:00:01 +00:00
|
|
|
$string = str_replace(
|
2023-01-11 20:16:35 +00:00
|
|
|
$double_chars['in'],
|
|
|
|
$double_chars['out'],
|
|
|
|
$string
|
2023-01-11 20:00:01 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $string;
|
|
|
|
}
|
|
|
|
/**
|
2023-04-20 19:42:56 +00:00
|
|
|
* Clean string from diacritics and punctuation.
|
2023-03-12 09:50:27 +00:00
|
|
|
*
|
2023-04-20 19:42:56 +00:00
|
|
|
* @param null|string $str The string
|
2023-03-12 09:50:27 +00:00
|
|
|
*
|
2023-04-20 19:42:56 +00:00
|
|
|
* @return null|string The cleaned string
|
2023-03-12 09:50:27 +00:00
|
|
|
* */
|
|
|
|
public static function cleanStr(?string $str): ?string
|
|
|
|
{
|
2023-04-26 18:45:01 +00:00
|
|
|
return is_null($str) ? null : self::charsToChar(self::removeAccents($str));
|
2023-03-12 09:50:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-04-20 19:42:56 +00:00
|
|
|
* Clean post URLs from diacritics and punctuation.
|
2023-01-11 20:16:35 +00:00
|
|
|
*
|
2023-10-07 20:18:57 +00:00
|
|
|
* @param Blog $blog The blog instance
|
2023-04-23 12:52:40 +00:00
|
|
|
* @param Cursor $cur The post Cursor
|
2023-01-11 20:00:01 +00:00
|
|
|
* */
|
2023-10-15 19:06:31 +00:00
|
|
|
public static function cleanPost(BlogInterface $blog, Cursor $cur): void
|
2023-01-11 20:00:01 +00:00
|
|
|
{
|
2023-04-20 19:42:56 +00:00
|
|
|
$cur->setField('post_url', self::cleanStr($cur->getField('post_url')));
|
2023-01-11 20:00:01 +00:00
|
|
|
}
|
|
|
|
}
|