SluggedBehavior.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2008, Andy Dawson
  4. * @author Andy Dawson
  5. * @author Mark Scherer
  6. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  7. */
  8. namespace Tools\Model\Behavior;
  9. use Cake\Event\Event;
  10. use Cake\ORM\Behavior;
  11. use Cake\ORM\Entity;
  12. use Cake\ORM\Query;
  13. use Cake\ORM\Table;
  14. use Cake\Utility\Inflector;
  15. use Cake\Core\Configure;
  16. use Cake\Error\Exception;
  17. /**
  18. * SluggedBehavior
  19. * Part based/inspired by the sluggable behavior of Mariano Iglesias
  20. */
  21. class SluggedBehavior extends Behavior {
  22. /**
  23. * Default settings
  24. *
  25. * label
  26. * set to the name of a field to use for the slug, an array of fields to use as slugs or leave as null to rely
  27. * on the format returned by find('list') to determine the string to use for slugs
  28. * overwrite has 2 values
  29. * false - once the slug has been saved, do not change it (use if you are doing lookups based on slugs)
  30. * true - if the label field values change, regenerate the slug (use if you are the slug is just window-dressing)
  31. * unique has 2 values
  32. * false - will not enforce a unique slug, whatever the label is is direclty slugged without checking for duplicates
  33. * true - use if you are doing lookups based on slugs (see overwrite)
  34. * mode has the following values
  35. * ascii - retuns an ascii slug generated using the core Inflector::slug() function
  36. * display - a dummy mode which returns a slug legal for display - removes illegal (not unprintable) characters
  37. * url - returns a slug appropriate to put in a URL
  38. * class - a dummy mode which returns a slug appropriate to put in a html class (there are no restrictions)
  39. * id - retuns a slug appropriate to use in a html id
  40. * case has the following values
  41. * null - don't change the case of the slug
  42. * low - force lower case. E.g. "this-is-the-slug"
  43. * up - force upper case E.g. "THIS-IS-THE-SLUG"
  44. * title - force title case. E.g. "This-Is-The-Slug"
  45. * camel - force CamelCase. E.g. "ThisIsTheSlug"
  46. *
  47. * @var array
  48. */
  49. protected $_defaultConfig = array(
  50. 'label' => null,
  51. 'field' => 'slug',
  52. 'overwriteField' => 'overwrite_slug',
  53. 'mode' => 'url',
  54. 'separator' => '-',
  55. 'defaultSuffix' => null,
  56. 'length' => null,
  57. 'overwrite' => false,
  58. 'unique' => false,
  59. 'notices' => true,
  60. 'case' => null,
  61. 'replace' => array(
  62. '&' => 'and',
  63. '+' => 'and',
  64. '#' => 'hash',
  65. ),
  66. //'run' => 'beforeValidate',
  67. 'language' => null,
  68. 'encoding' => null,
  69. 'scope' => array(),
  70. 'tidy' => true,
  71. //'implementedFinders' => ['slugged' => 'findSlugged'],
  72. );
  73. /**
  74. * Table instance
  75. *
  76. * @var \Cake\ORM\Table
  77. */
  78. protected $_table;
  79. public function __construct(Table $table, array $config = []) {
  80. $this->_defaultConfig['notices'] = Configure::read('debug');
  81. $this->_defaultConfig['label'] = $table->displayField();
  82. foreach ($this->_defaultConfig['replace'] as $key => $value) {
  83. $this->_defaultConfig['replace'][$key] = __($value);
  84. }
  85. $config += (array)Configure::read('Slugged');
  86. parent::__construct($table, $config);
  87. if (!$this->_config['length']) {
  88. $length = $table->schema()->column($this->_config['field'])['length'];
  89. if (!$length) {
  90. $length = 255;
  91. }
  92. $this->_config['length'] = $length;
  93. }
  94. $this->_table = $table;
  95. }
  96. /**
  97. * Setup method
  98. *
  99. * Use the model's label field as the default field on which to base the slug, the label can be made up of multiple
  100. * fields by specifying an array of fields
  101. *
  102. * @param Model $Model
  103. * @param array $config
  104. * @return void
  105. */
  106. public function __setup(Model $Model, $config = array()) {
  107. $label = $this->_config['label'] = (array)$this->_config['label'];
  108. if ($Model->Behaviors->loaded('Translate')) {
  109. $notices = false;
  110. }
  111. if ($notices) {
  112. foreach ($label as $field) {
  113. $alias = $this->_table->alias();
  114. if (strpos($field, '.')) {
  115. list($alias, $field) = explode('.', $field);
  116. if (!$Model->$alias->hasField($field)) {
  117. throw new Exception('(SluggedBehavior::setup) model ' . $Model->$alias->name . ' is missing the field ' . $field .
  118. ' (specified in the setup for model ' . $Model->name . ') ');
  119. }
  120. } elseif (!$Model->hasField($field)) {
  121. throw new Exception('(SluggedBehavior::setup) model ' . $Model->name . ' is missing the field ' . $field . ' specified in the setup.');
  122. }
  123. }
  124. }
  125. }
  126. public function findSlugged(Query $query, array $options) {
  127. return $query->where([$this->_config['field'] => $options['slug']]);
  128. }
  129. /**
  130. * SluggedBehavior::beforeSave()
  131. *
  132. * @param mixed $event
  133. * @param mixed $entity
  134. * @return void
  135. */
  136. public function beforeSave(Event $event, Entity $entity) {
  137. $this->slug($entity);
  138. }
  139. public function slug(Entity $entity) {
  140. foreach ((array)$this->_config['label'] as $k => $v) {
  141. break;
  142. }
  143. $value = $entity->get($v);
  144. $entity->set($this->_config['field'], $this->generateSlug($entity, $value));
  145. }
  146. /**
  147. * Generate slug method
  148. *
  149. * If a new row, or overwrite is set to true, check for a change to a label field and add the slug to the data
  150. * to be saved
  151. *
  152. * @param Model $Model
  153. * @return void
  154. */
  155. public function _generateSlug(Model $Model) {
  156. extract($this->_config);
  157. if ($notices && !$Model->hasField($slugField)) {
  158. return;
  159. }
  160. if (!$overwrite && !empty($Model->data[$this->_table->alias()][$overwriteField])) {
  161. $overwrite = true;
  162. }
  163. if ($overwrite || !$Model->id) {
  164. if ($label) {
  165. $somethingToDo = false;
  166. foreach ($label as $field) {
  167. $alias = $this->_table->alias();
  168. if (strpos($field, '.') !== false) {
  169. list($alias, $field) = explode('.', $field, 2);
  170. }
  171. if (isset($Model->data[$alias][$field])) {
  172. $somethingToDo = true;
  173. }
  174. }
  175. if (!$somethingToDo) {
  176. return;
  177. }
  178. $slug = array();
  179. foreach ($label as $field) {
  180. $alias = $this->_table->alias();
  181. if (strpos($field, '.')) {
  182. list($alias, $field) = explode('.', $field);
  183. }
  184. if (isset($Model->data[$alias][$field])) {
  185. if (is_array($Model->data[$alias][$field])) {
  186. return $this->_multiSlug($Model);
  187. }
  188. $slug[] = $Model->data[$alias][$field];
  189. } elseif ($Model->id) {
  190. $slug[] = $Model->field($field);
  191. }
  192. }
  193. $slug = implode($slug, $separator);
  194. } else {
  195. $slug = $this->display($Model);
  196. }
  197. $slug = $Model->slug($slug);
  198. $this->_addToWhitelist($Model, array($slugField));
  199. $Model->data[$this->_table->alias()][$slugField] = $slug;
  200. }
  201. }
  202. /**
  203. * Slug method
  204. *
  205. * For the given string, generate a slug. The replacements used are based on the mode setting, If tidy is false
  206. * (only possible if directly called - primarily for tracing and testing) separators will not be cleaned up
  207. * and so slugs like "-----as---df-----" are possible, which by default would otherwise be returned as "as-df".
  208. * If the mode is "id" and the first charcter of the regex-ed slug is numeric, it will be prefixed with an x.
  209. * If unique is set to true, check for a unique slug and if unavailable suffix the slug with -1, -2, -3 etc.
  210. * until a unique slug is found
  211. *
  212. * @param Model $Model
  213. * @param mixed $string
  214. * @param boolean $tidy
  215. * @return string a slug
  216. */
  217. public function generateSlug(Entity $entity, $value) {
  218. $separator = $this->_config['separator'];
  219. $case = $this->_config['separator'];
  220. $string = str_replace(array("\r\n", "\r", "\n"), ' ', $value);
  221. if ($replace = $this->_config['replace']) {
  222. $string = str_replace(array_keys($replace), array_values($replace), $string);
  223. }
  224. if ($this->_config['mode'] === 'ascii') {
  225. $slug = Inflector::slug($string, $separator);
  226. } else {
  227. $regex = $this->_regex($this->_config['mode']);
  228. if ($regex) {
  229. $slug = $this->_pregReplace('@[' . $regex . ']@Su', $separator, $string);
  230. } else {
  231. $slug = $string;
  232. }
  233. }
  234. if ($this->_config['tidy']) {
  235. $slug = $this->_pregReplace('/' . $separator . '+/', $separator, $slug);
  236. $slug = trim($slug, $separator);
  237. if ($slug && $this->_config['mode'] === 'id' && is_numeric($slug[0])) {
  238. $slug = 'x' . $slug;
  239. }
  240. }
  241. if (strlen($slug) > $this->_config['length']) {
  242. $slug = mb_substr($slug, 0, $this->_config['length']);
  243. while ($slug && strlen($slug) > $this->_config['length']) {
  244. $slug = mb_substr($slug, 0, mb_strlen($slug) - 1);
  245. }
  246. }
  247. if ($case) {
  248. if ($case === 'up') {
  249. $slug = mb_strtoupper($slug);
  250. } else {
  251. $slug = mb_strtolower($slug);
  252. }
  253. if (in_array($case, array('title', 'camel'))) {
  254. $words = explode($separator, $slug);
  255. foreach ($words as $i => &$word) {
  256. $firstChar = mb_substr($word, 0, 1);
  257. $rest = mb_substr($word, 1, mb_strlen($word) - 1);
  258. $firstCharUp = mb_strtoupper($firstChar);
  259. $word = $firstCharUp . $rest;
  260. }
  261. if ($case === 'title') {
  262. $slug = implode($words, $separator);
  263. } elseif ($case === 'camel') {
  264. $slug = implode($words);
  265. }
  266. }
  267. }
  268. if ($this->_config['unique']) {
  269. $field = $this->_table->alias() . '.' . $this->_config['field'];
  270. $conditions = array($field => $slug);
  271. $conditions = array_merge($conditions, $this->_config['scope']);
  272. if ($id = $entity->get($this->_table->primaryKey())) {
  273. $conditions['NOT'][$this->_table->alias() . '.' . $this->_table->primaryKey()] = $id;
  274. }
  275. $i = 0;
  276. $suffix = '';
  277. while ($this->_table->exists($conditions)) {
  278. $i++;
  279. $suffix = $separator . $i;
  280. if (strlen($slug . $suffix) > $this->_config['length']) {
  281. $slug = substr($slug, 0, $this->_config['length'] - strlen($suffix));
  282. }
  283. $conditions[$field] = $slug . $suffix;
  284. }
  285. if ($suffix) {
  286. $slug .= $suffix;
  287. }
  288. }
  289. return $slug;
  290. }
  291. /**
  292. * Display method
  293. *
  294. * Cheat - use find('list') and assume it has been modified such that lists show in the desired format.
  295. * First check (since this method is called in beforeSave) if there is data to be saved, and use that
  296. * to get the display name
  297. * Otherwise, read from the database
  298. *
  299. * @param mixed $id
  300. * @return mixed string (the display name) or false
  301. */
  302. public function display(Model $Model, $id = null) {
  303. if (!$id) {
  304. if (!$Model->id) {
  305. return false;
  306. }
  307. $id = $Model->id;
  308. }
  309. $conditions = array_merge(array(
  310. $this->_table->alias() . '.' . $this->_table->primaryKey() => $id),
  311. $this->_config['scope']);
  312. return current($Model->find('list', array('conditions' => $conditions)));
  313. }
  314. /**
  315. * ResetSlugs method.
  316. *
  317. * Regenerate all slugs. On large dbs this can take more than 30 seconds - a time
  318. * limit is set to allow a minimum 100 updates per second as a preventative measure.
  319. *
  320. * @param AppModel $Model
  321. * @param array $conditions
  322. * @param integer $recursive
  323. * @return boolean Success
  324. */
  325. public function resetSlugs(Model $Model, $params = array()) {
  326. $recursive = -1;
  327. extract($this->_config);
  328. if (!$Model->hasField($slugField)) {
  329. return false;
  330. }
  331. $defaults = array(
  332. 'page' => 1,
  333. 'limit' => 100,
  334. 'fields' => array_merge(array($this->_table->primaryKey()), $label),
  335. 'order' => $Model->displayField . ' ASC',
  336. 'conditions' => $scope,
  337. 'recursive' => $recursive,
  338. 'overwrite' => true,
  339. );
  340. $params = array_merge($defaults, $params);
  341. $count = $Model->find('count', compact('conditions'));
  342. $max = ini_get('max_execution_time');
  343. if ($max) {
  344. set_time_limit(max($max, $count / 100));
  345. }
  346. $settings = $Model->Behaviors->Slugged->settings[$this->_table->alias()];
  347. $Model->Behaviors->load('Tools.Slugged', $params + $settings);
  348. while ($rows = $Model->find('all', $params)) {
  349. foreach ($rows as $row) {
  350. $Model->create();
  351. $options = array(
  352. 'validate' => true,
  353. 'fieldList' => array_merge(array($this->_table->primaryKey(), $slugField), $label)
  354. );
  355. if (!$Model->save($row, $options)) {
  356. throw new RuntimeException(print_r($row[$this->_table->alias()], true) . ': ' . print_r($Model->validationErrors, true));
  357. }
  358. }
  359. $params['page']++;
  360. }
  361. return true;
  362. }
  363. /**
  364. * Multi slug method
  365. *
  366. * Handle both slug and label fields using the translate behavior, and being edited
  367. * in multiple locales at once
  368. *
  369. * @param Model $Model
  370. * @return void
  371. */
  372. protected function _multiSlug(Model $Model) {
  373. extract($this->_config);
  374. $data = $Model->data;
  375. $field = current($label);
  376. foreach ($Model->data[$this->_table->alias()][$field] as $locale => $_) {
  377. foreach ($label as $field) {
  378. if (is_array($data[$this->_table->alias()][$field])) {
  379. $Model->data[$this->_table->alias()][$field] = $Model->slug($data[$this->_table->alias()][$field][$locale]);
  380. }
  381. }
  382. $this->beforeValidate($Model);
  383. $data[$this->_table->alias()][$slugField][$locale] = $Model->data[$this->_table->alias()][$field];
  384. }
  385. $Model->data = $data;
  386. }
  387. /**
  388. * Wrapper for preg replace taking care of encoding
  389. *
  390. * @param mixed $pattern
  391. * @param mixed $replace
  392. * @param mixed $string
  393. * @param string $encoding
  394. * @return void
  395. */
  396. protected function _pregReplace($pattern, $replace, $string) {
  397. return preg_replace($pattern, $replace, $string);
  398. }
  399. /**
  400. * Regex method
  401. *
  402. * Based upon the mode return a partial regex to generate a valid string for the intended use. Note that you
  403. * can use almost litterally anything in a url - the limitation is only in what your own application
  404. * understands. See the test case for info on how these regex patterns were generated.
  405. *
  406. * @param string $mode
  407. * @return string a partial regex or false on failure
  408. */
  409. protected function _regex($mode) {
  410. $return = '\x00-\x1f\x26\x3c\x7f-\x9f\x{fffe}-\x{ffff}';
  411. if ($mode === 'display') {
  412. return $return;
  413. }
  414. $return .= preg_quote(' \'"/?<>.$/:;?@=+&%\#', '@');
  415. if ($mode === 'url') {
  416. return $return;
  417. }
  418. $return .= '';
  419. if ($mode === 'class') {
  420. return $return;
  421. }
  422. if ($mode === 'id') {
  423. return '\x{0000}-\x{002f}\x{003a}-\x{0040}\x{005b}-\x{005e}\x{0060}\x{007b}-\x{007e}\x{00a0}-\x{00b6}' .
  424. '\x{00b8}-\x{00bf}\x{00d7}\x{00f7}\x{0132}-\x{0133}\x{013f}-\x{0140}\x{0149}\x{017f}\x{01c4}-\x{01cc}' .
  425. '\x{01f1}-\x{01f3}\x{01f6}-\x{01f9}\x{0218}-\x{024f}\x{02a9}-\x{02ba}\x{02c2}-\x{02cf}\x{02d2}-\x{02ff}' .
  426. '\x{0346}-\x{035f}\x{0362}-\x{0385}\x{038b}\x{038d}\x{03a2}\x{03cf}\x{03d7}-\x{03d9}\x{03db}\x{03dd}\x{03df}' .
  427. '\x{03e1}\x{03f4}-\x{0400}\x{040d}\x{0450}\x{045d}\x{0482}\x{0487}-\x{048f}\x{04c5}-\x{04c6}\x{04c9}-\x{04ca}' .
  428. '\x{04cd}-\x{04cf}\x{04ec}-\x{04ed}\x{04f6}-\x{04f7}\x{04fa}-\x{0530}\x{0557}-\x{0558}\x{055a}-\x{0560}' .
  429. '\x{0587}-\x{0590}\x{05a2}\x{05ba}\x{05be}\x{05c0}\x{05c3}\x{05c5}-\x{05cf}\x{05eb}-\x{05ef}\x{05f3}-\x{0620}' .
  430. '\x{063b}-\x{063f}\x{0653}-\x{065f}\x{066a}-\x{066f}\x{06b8}-\x{06b9}\x{06bf}\x{06cf}\x{06d4}\x{06e9}' .
  431. '\x{06ee}-\x{06ef}\x{06fa}-\x{0900}\x{0904}\x{093a}-\x{093b}\x{094e}-\x{0950}\x{0955}-\x{0957}' .
  432. '\x{0964}-\x{0965}\x{0970}-\x{0980}\x{0984}\x{098d}-\x{098e}\x{0991}-\x{0992}\x{09a9}\x{09b1}\x{09b3}-\x{09b5}' .
  433. '\x{09ba}-\x{09bb}\x{09bd}\x{09c5}-\x{09c6}\x{09c9}-\x{09ca}\x{09ce}-\x{09d6}\x{09d8}-\x{09db}\x{09de}' .
  434. '\x{09e4}-\x{09e5}\x{09f2}-\x{0a01}\x{0a03}-\x{0a04}\x{0a0b}-\x{0a0e}\x{0a11}-\x{0a12}\x{0a29}\x{0a31}\x{0a34}' .
  435. '\x{0a37}\x{0a3a}-\x{0a3b}\x{0a3d}\x{0a43}-\x{0a46}\x{0a49}-\x{0a4a}\x{0a4e}-\x{0a58}\x{0a5d}\x{0a5f}-\x{0a65}' .
  436. '\x{0a75}-\x{0a80}\x{0a84}\x{0a8c}\x{0a8e}\x{0a92}\x{0aa9}\x{0ab1}\x{0ab4}\x{0aba}-\x{0abb}\x{0ac6}\x{0aca}' .
  437. '\x{0ace}-\x{0adf}\x{0ae1}-\x{0ae5}\x{0af0}-\x{0b00}\x{0b04}\x{0b0d}-\x{0b0e}\x{0b11}-\x{0b12}\x{0b29}\x{0b31}' .
  438. '\x{0b34}-\x{0b35}\x{0b3a}-\x{0b3b}\x{0b44}-\x{0b46}\x{0b49}-\x{0b4a}\x{0b4e}-\x{0b55}\x{0b58}-\x{0b5b}\x{0b5e}' .
  439. '\x{0b62}-\x{0b65}\x{0b70}-\x{0b81}\x{0b84}\x{0b8b}-\x{0b8d}\x{0b91}\x{0b96}-\x{0b98}\x{0b9b}\x{0b9d}' .
  440. '\x{0ba0}-\x{0ba2}\x{0ba5}-\x{0ba7}\x{0bab}-\x{0bad}\x{0bb6}\x{0bba}-\x{0bbd}\x{0bc3}-\x{0bc5}\x{0bc9}' .
  441. '\x{0bce}-\x{0bd6}\x{0bd8}-\x{0be6}\x{0bf0}-\x{0c00}\x{0c04}\x{0c0d}\x{0c11}\x{0c29}\x{0c34}\x{0c3a}-\x{0c3d}' .
  442. '\x{0c45}\x{0c49}\x{0c4e}-\x{0c54}\x{0c57}-\x{0c5f}\x{0c62}-\x{0c65}\x{0c70}-\x{0c81}\x{0c84}\x{0c8d}\x{0c91}' .
  443. '\x{0ca9}\x{0cb4}\x{0cba}-\x{0cbd}\x{0cc5}\x{0cc9}\x{0cce}-\x{0cd4}\x{0cd7}-\x{0cdd}\x{0cdf}\x{0ce2}-\x{0ce5}' .
  444. '\x{0cf0}-\x{0d01}\x{0d04}\x{0d0d}\x{0d11}\x{0d29}\x{0d3a}-\x{0d3d}\x{0d44}-\x{0d45}\x{0d49}\x{0d4e}-\x{0d56}' .
  445. '\x{0d58}-\x{0d5f}\x{0d62}-\x{0d65}\x{0d70}-\x{0e00}\x{0e2f}\x{0e3b}-\x{0e3f}\x{0e4f}\x{0e5a}-\x{0e80}\x{0e83}' .
  446. '\x{0e85}-\x{0e86}\x{0e89}\x{0e8b}-\x{0e8c}\x{0e8e}-\x{0e93}\x{0e98}\x{0ea0}\x{0ea4}\x{0ea6}\x{0ea8}-\x{0ea9}' .
  447. '\x{0eac}\x{0eaf}\x{0eba}\x{0ebe}-\x{0ebf}\x{0ec5}\x{0ec7}\x{0ece}-\x{0ecf}\x{0eda}-\x{0f17}\x{0f1a}-\x{0f1f}' .
  448. '\x{0f2a}-\x{0f34}\x{0f36}\x{0f38}\x{0f3a}-\x{0f3d}\x{0f48}\x{0f6a}-\x{0f70}\x{0f85}\x{0f8c}-\x{0f8f}\x{0f96}' .
  449. '\x{0f98}\x{0fae}-\x{0fb0}\x{0fb8}\x{0fba}-\x{109f}\x{10c6}-\x{10cf}\x{10f7}-\x{10ff}\x{1101}\x{1104}\x{1108}' .
  450. '\x{110a}\x{110d}\x{1113}-\x{113b}\x{113d}\x{113f}\x{1141}-\x{114b}\x{114d}\x{114f}\x{1151}-\x{1153}' .
  451. '\x{1156}-\x{1158}\x{115a}-\x{115e}\x{1162}\x{1164}\x{1166}\x{1168}\x{116a}-\x{116c}\x{116f}-\x{1171}\x{1174}' .
  452. '\x{1176}-\x{119d}\x{119f}-\x{11a7}\x{11a9}-\x{11aa}\x{11ac}-\x{11ad}\x{11b0}-\x{11b6}\x{11b9}\x{11bb}' .
  453. '\x{11c3}-\x{11ea}\x{11ec}-\x{11ef}\x{11f1}-\x{11f8}\x{11fa}-\x{1dff}\x{1e9c}-\x{1e9f}\x{1efa}-\x{1eff}' .
  454. '\x{1f16}-\x{1f17}\x{1f1e}-\x{1f1f}\x{1f46}-\x{1f47}\x{1f4e}-\x{1f4f}\x{1f58}\x{1f5a}\x{1f5c}\x{1f5e}' .
  455. '\x{1f7e}-\x{1f7f}\x{1fb5}\x{1fbd}\x{1fbf}-\x{1fc1}\x{1fc5}\x{1fcd}-\x{1fcf}\x{1fd4}-\x{1fd5}\x{1fdc}-\x{1fdf}' .
  456. '\x{1fed}-\x{1ff1}\x{1ff5}\x{1ffd}-\x{20cf}\x{20dd}-\x{20e0}\x{20e2}-\x{2125}\x{2127}-\x{2129}' .
  457. '\x{212c}-\x{212d}\x{212f}-\x{217f}\x{2183}-\x{3004}\x{3006}\x{3008}-\x{3020}\x{3030}\x{3036}-\x{3040}' .
  458. '\x{3095}-\x{3098}\x{309b}-\x{309c}\x{309f}-\x{30a0}\x{30fb}\x{30ff}-\x{3104}\x{312d}-\x{4dff}' .
  459. '\x{9fa6}-\x{abff}\x{d7a4}-\x{d7ff}\x{e000}-\x{ffff}';
  460. }
  461. return false;
  462. }
  463. }