PageRenderTime 57ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-content/plugins/wp-to-twitter/wpt-functions.php

https://github.com/CaffeinatedJim/catsinmyyard
PHP | 745 lines | 585 code | 95 blank | 65 comment | 143 complexity | d1f4c8a51e4b846b365ff78cdb169f04 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, GPL-3.0, BSD-3-Clause, Apache-2.0, MIT, AGPL-1.0, LGPL-2.1
  1. <?php
  2. // This file contains secondary functions supporting WP to Twitter
  3. if ( ! defined( 'ABSPATH' ) ) {
  4. exit;
  5. } // Exit if accessed directly
  6. function wpt_mail( $subject, $body, $override=false ) {
  7. if ( ( WPT_DEBUG && function_exists( 'wpt_pro_exists' ) ) || $override == true ) {
  8. $use_email = true;
  9. if ( $use_email ) {
  10. wp_mail( WPT_DEBUG_ADDRESS, $subject, $body, WPT_FROM );
  11. } else {
  12. $debug = get_option( 'wpt_debug' );
  13. $debug[ date( 'Y-m-d H:i:s' ) ] = array( $subject, $body );
  14. update_option( 'wpt_debug', $debug );
  15. }
  16. }
  17. }
  18. function jd_remote_json( $url, $array = true ) {
  19. $input = jd_fetch_url( $url );
  20. $obj = json_decode( $input, $array );
  21. if ( function_exists( 'json_last_error' ) ) { // > PHP 5.3
  22. try {
  23. if ( is_null( $obj ) ) {
  24. switch ( json_last_error() ) {
  25. case JSON_ERROR_DEPTH :
  26. $msg = ' - Maximum stack depth exceeded';
  27. break;
  28. case JSON_ERROR_STATE_MISMATCH :
  29. $msg = ' - Underflow or the modes mismatch';
  30. break;
  31. case JSON_ERROR_CTRL_CHAR :
  32. $msg = ' - Unexpected control character found';
  33. break;
  34. case JSON_ERROR_SYNTAX :
  35. $msg = ' - Syntax error, malformed JSON';
  36. break;
  37. case JSON_ERROR_UTF8 :
  38. $msg = ' - Malformed UTF-8 characters, possibly incorrectly encoded';
  39. break;
  40. default :
  41. $msg = ' - Unknown error';
  42. break;
  43. }
  44. throw new Exception( $msg );
  45. }
  46. } catch ( Exception $e ) {
  47. return $e->getMessage();
  48. }
  49. }
  50. return $obj;
  51. }
  52. function is_valid_url( $url ) {
  53. if ( is_string( $url ) ) {
  54. $url = urldecode( $url );
  55. return preg_match( '|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)?$|i', $url );
  56. } else {
  57. return false;
  58. }
  59. }
  60. // Fetch a remote page. Input url, return content
  61. function jd_fetch_url( $url, $method = 'GET', $body = '', $headers = '', $return = 'body' ) {
  62. $request = new WP_Http;
  63. $result = $request->request( $url, array( 'method' => $method,
  64. 'body' => $body,
  65. 'headers' => $headers,
  66. 'sslverify' => false,
  67. 'user-agent' => 'WP to Twitter/http://www.joedolson.com/wp-to-twitter/'
  68. ) );
  69. // Success?
  70. if ( ! is_wp_error( $result ) && isset( $result['body'] ) ) {
  71. if ( $result['response']['code'] == 200 ) {
  72. if ( $return == 'body' ) {
  73. return $result['body'];
  74. } else {
  75. return $result;
  76. }
  77. } else {
  78. return $result['response']['code'];
  79. }
  80. // Failure (server problem...)
  81. } else {
  82. return false;
  83. }
  84. }
  85. if ( ! function_exists( 'mb_strlen' ) ) {
  86. /**
  87. * Fallback implementation of mb_strlen, hardcoded to UTF-8.
  88. *
  89. * @param string $str
  90. *
  91. * @return int
  92. */
  93. function mb_strlen( $str ) {
  94. $counts = count_chars( $str );
  95. $total = 0;
  96. // Count ASCII bytes
  97. for ( $i = 0; $i < 0x80; $i ++ ) {
  98. $total += $counts[ $i ];
  99. }
  100. // Count multibyte sequence heads
  101. for ( $i = 0xc0; $i < 0xff; $i ++ ) {
  102. $total += $counts[ $i ];
  103. }
  104. return $total;
  105. }
  106. }
  107. if ( ! function_exists( 'mb_substr' ) ) {
  108. function mb_substr( $str, $start, $count = 'end' ) {
  109. if ( $start != 0 ) {
  110. $split = self::mb_substr_split_unicode( $str, intval( $start ) );
  111. $str = substr( $str, $split );
  112. }
  113. if ( $count !== 'end' ) {
  114. $split = self::mb_substr_split_unicode( $str, intval( $count ) );
  115. $str = substr( $str, 0, $split );
  116. }
  117. return $str;
  118. }
  119. }
  120. // filter_var substitution for PHP <5.2
  121. if ( ! function_exists( 'filter_var' ) ) {
  122. function filter_var( $url ) {
  123. // this does not emulate filter_var; merely the usage of filter_var in WP to Twitter.
  124. return ( stripos( $url, 'https:' ) !== false || stripos( $url, 'http:' ) !== false ) ? true : false;
  125. }
  126. }
  127. if ( ! function_exists( 'mb_strrpos' ) ) {
  128. /**
  129. * Fallback implementation of mb_strrpos, hardcoded to UTF-8.
  130. *
  131. * @param $haystack String
  132. * @param $needle String
  133. * @param $offset integer: optional start position
  134. *
  135. * @return int
  136. */
  137. function mb_strrpos( $haystack, $needle, $offset = 0 ) {
  138. $needle = preg_quote( $needle, '/' );
  139. $ar = array();
  140. preg_match_all( '/' . $needle . '/u', $haystack, $ar, PREG_OFFSET_CAPTURE, $offset );
  141. if ( isset( $ar[0] ) && count( $ar[0] ) > 0 &&
  142. isset( $ar[0][ count( $ar[0] ) - 1 ][1] )
  143. ) {
  144. return $ar[0][ count( $ar[0] ) - 1 ][1];
  145. } else {
  146. return false;
  147. }
  148. }
  149. }
  150. // str_ireplace substitution for PHP4
  151. if ( ! function_exists( 'str_ireplace' ) ) {
  152. function str_ireplace( $needle, $str, $haystack ) {
  153. $needle = preg_quote( $needle, '/' );
  154. return preg_replace( "/$needle/i", $str, $haystack );
  155. }
  156. }
  157. // str_split substitution for PHP4
  158. if ( ! function_exists( 'str_split' ) ) {
  159. function str_split( $string, $string_length = 1 ) {
  160. if ( strlen( $string ) > $string_length || ! $string_length ) {
  161. do {
  162. $parts[] = substr( $string, 0, $string_length );
  163. $string = substr( $string, $string_length );
  164. } while ( $string !== false );
  165. } else {
  166. $parts = array( $string );
  167. }
  168. return $parts;
  169. }
  170. }
  171. // mb_substr_replace substition for PHP4
  172. if ( ! function_exists( 'mb_substr_replace' ) ) {
  173. function mb_substr_replace( $string, $replacement, $start, $length = null, $encoding = null ) {
  174. if ( extension_loaded( 'mbstring' ) === true ) {
  175. $string_length = ( is_null( $encoding ) === true ) ? mb_strlen( $string ) : mb_strlen( $string, $encoding );
  176. if ( $start < 0 ) {
  177. $start = max( 0, $string_length + $start );
  178. } else if ( $start > $string_length ) {
  179. $start = $string_length;
  180. }
  181. if ( $length < 0 ) {
  182. $length = max( 0, $string_length - $start + $length );
  183. } else if ( ( is_null( $length ) === true ) || ( $length > $string_length ) ) {
  184. $length = $string_length;
  185. }
  186. if ( ( $start + $length ) > $string_length ) {
  187. $length = $string_length - $start;
  188. }
  189. if ( is_null( $encoding ) === true ) {
  190. return mb_substr( $string, 0, $start ) . $replacement . mb_substr( $string, $start + $length, $string_length - $start - $length );
  191. }
  192. return mb_substr( $string, 0, $start, $encoding ) . $replacement . mb_substr( $string, $start + $length, $string_length - $start - $length, $encoding );
  193. }
  194. return ( is_null( $length ) === true ) ? substr_replace( $string, $replacement, $start ) : substr_replace( $string, $replacement, $start, $length );
  195. }
  196. }
  197. /**
  198. * This function is obsolete; only exists for people using out of date versions of WP Tweets PRO.
  199. */
  200. function wtt_option_selected( $field, $value, $type = 'checkbox' ) {
  201. switch ( $type ) {
  202. case 'radio':
  203. case 'checkbox':
  204. $result = ' checked="checked"';
  205. break;
  206. case 'option':
  207. $result = ' selected="selected"';
  208. break;
  209. default: $result = ' selected="selected"';
  210. }
  211. if ( $field == $value ) {
  212. $output = $result;
  213. } else {
  214. $output = '';
  215. }
  216. return $output;
  217. }
  218. /**
  219. * Compares two dates to identify which is earlier. Used to differentiate between post edits and original publication.
  220. *
  221. * @param string $early
  222. * @param string $late
  223. *
  224. * @return integer 1|0
  225. */
  226. function wpt_date_compare( $early, $late ) {
  227. $modifier = apply_filters( 'wpt_edit_sensitivity', 0 ); // alter time in seconds to modified date.
  228. $firstdate = strtotime( $early );
  229. $lastdate = strtotime( $late ) + $modifier;
  230. if ( $firstdate <= $lastdate ) { // if post_modified is before or equal to post_date
  231. return 1;
  232. } else {
  233. return 0;
  234. }
  235. }
  236. /**
  237. * Gets the first attachment for the supplied post.
  238. *
  239. * @param integer $post_ID The post ID
  240. *
  241. * @return mixed boolean|integer Attachment ID.
  242. */
  243. function wpt_post_attachment( $post_ID ) {
  244. $use_featured_image = apply_filters( 'wpt_use_featured_image', true, $post_ID );
  245. if ( has_post_thumbnail( $post_ID ) && $use_featured_image ) {
  246. $attachment = get_post_thumbnail_id( $post_ID );
  247. $return = $attachment;
  248. } else {
  249. $args = array(
  250. 'post_type' => 'attachment',
  251. 'numberposts' => 1,
  252. 'post_status' => 'published',
  253. 'post_parent' => $post_ID,
  254. 'post_mime_type' => 'image',
  255. 'order' => 'ASC'
  256. );
  257. $attachments = get_posts( $args );
  258. if ( $attachments ) {
  259. $return = $attachments[0]->ID; //Return the first attachment.
  260. } else {
  261. $return = false;
  262. }
  263. }
  264. return apply_filters( 'wpt_post_attachment', $return, $post_ID );
  265. }
  266. function wpt_get_support_form() {
  267. global $current_user, $wpt_version;
  268. get_currentuserinfo();
  269. $request = '';
  270. // send fields for WP to Twitter
  271. $license = ( get_option( 'wpt_license_key' ) != '' ) ? get_option( 'wpt_license_key' ) : 'none';
  272. if ( $license != '' ) {
  273. $valid = ( get_option( 'wpt_license_valid' ) == 'true' ) ? ' (valid)' : ' (invalid)';
  274. } else {
  275. $valid = '';
  276. }
  277. $license = "License Key: " . $license . $valid;
  278. $version = $wpt_version;
  279. $wtt_twitter_username = get_option( 'wtt_twitter_username' );
  280. // send fields for all plugins
  281. $wp_version = get_bloginfo( 'version' );
  282. $home_url = home_url();
  283. $wp_url = site_url();
  284. $language = get_bloginfo( 'language' );
  285. $charset = get_bloginfo( 'charset' );
  286. // server
  287. $php_version = phpversion();
  288. // theme data
  289. $theme = wp_get_theme();
  290. $theme_name = $theme->Name;
  291. $theme_uri = $theme->ThemeURI;
  292. $theme_parent = $theme->Template;
  293. $theme_version = $theme->Version;
  294. $admin_email = get_option( 'admin_email' );
  295. // plugin data
  296. $plugins = get_plugins();
  297. $plugins_string = '';
  298. foreach ( array_keys( $plugins ) as $key ) {
  299. if ( is_plugin_active( $key ) ) {
  300. $plugin =& $plugins[ $key ];
  301. $plugin_name = $plugin['Name'];
  302. $plugin_uri = $plugin['PluginURI'];
  303. $plugin_version = $plugin['Version'];
  304. $plugins_string .= "$plugin_name: $plugin_version; $plugin_uri\n";
  305. }
  306. }
  307. $data = "
  308. ================ Installation Data ====================
  309. ==WP to Twitter==
  310. Version: $version
  311. Twitter username: http://twitter.com/$wtt_twitter_username
  312. $license
  313. ==WordPress:==
  314. Version: $wp_version
  315. URL: $home_url
  316. Install: $wp_url
  317. Language: $language
  318. Charset: $charset
  319. User Email: $current_user->user_email
  320. Admin Email: $admin_email
  321. ==Extra info:==
  322. PHP Version: $php_version
  323. Server Software: $_SERVER[SERVER_SOFTWARE]
  324. User Agent: $_SERVER[HTTP_USER_AGENT]
  325. ==Theme:==
  326. Name: $theme_name
  327. URI: $theme_uri
  328. Parent: $theme_parent
  329. Version: $theme_version
  330. ==Active Plugins:==
  331. $plugins_string
  332. ";
  333. if ( isset( $_POST['wpt_support'] ) ) {
  334. $nonce = $_REQUEST['_wpnonce'];
  335. if ( ! wp_verify_nonce( $nonce, 'wp-to-twitter-nonce' ) ) {
  336. die( "Security check failed" );
  337. }
  338. $request = ( ! empty( $_POST['support_request'] ) ) ? stripslashes( $_POST['support_request'] ) : false;
  339. $has_donated = ( isset( $_POST['has_donated'] ) ) ? "Donor" : "No donation";
  340. $has_read_faq = ( isset( $_POST['has_read_faq'] ) ) ? "Read FAQ" : false;
  341. if ( function_exists( 'wpt_pro_exists' ) && wpt_pro_exists() == true ) {
  342. $pro = " PRO";
  343. } else {
  344. $pro = '';
  345. }
  346. $subject = "WP to Twitter$pro support request. $has_donated";
  347. $message = $request . "\n\n" . $data;
  348. // Get the site domain and get rid of www. from pluggable.php
  349. $sitename = strtolower( $_SERVER['SERVER_NAME'] );
  350. if ( substr( $sitename, 0, 4 ) == 'www.' ) {
  351. $sitename = substr( $sitename, 4 );
  352. }
  353. $from_email = 'wordpress@' . $sitename;
  354. $from = "From: \"$current_user->display_name\" <$from_email>\r\nReply-to: \"$current_user->display_name\" <$current_user->user_email>\r\n";
  355. if ( ! $has_read_faq ) {
  356. echo "<div class='message error'><p>" . __( 'Please read the FAQ and other Help documents before making a support request.', 'wp-to-twitter' ) . "</p></div>";
  357. } else if ( ! $request ) {
  358. echo "<div class='message error'><p>" . __( 'Please describe your problem. I\'m not psychic.', 'wp-to-twitter' ) . "</p></div>";
  359. } else {
  360. $sent = wp_mail( "plugins@joedolson.com", $subject, $message, $from );
  361. if ( $sent ) {
  362. if ( $has_donated == 'Donor' ) {
  363. echo "<div class='message updated'><p>" . sprintf( __( 'Thank you for supporting the continuing development of this plug-in! I\'ll get back to you as soon as I can. Please ensure that you can receive email at <code>%s</code>.', 'wp-to-twitter' ), $current_user->user_email ) . "</p></div>";
  364. } else {
  365. echo "<div class='message updated'><p>" . sprintf( __( "Thanks for using WP to Twitter. Please ensure that you can receive email at <code>%s</code>.", 'wp-to-twitter' ), $current_user->user_email ) . "</p></div>";
  366. }
  367. } else {
  368. echo "<div class='message error'><p>" . __( "Sorry! I couldn't send that message. Here's the text of your request:", 'my-calendar' ) . "</p><p>" . sprintf( __( '<a href="%s">Contact me here</a>, instead</p>', 'wp-to-twitter' ), 'https://www.joedolson.com/contact/' ) . "<pre>$request</pre></div>";
  369. }
  370. }
  371. }
  372. if ( function_exists( 'wpt_pro_exists' ) && wpt_pro_exists() == true ) {
  373. $checked = "checked='checked'";
  374. } else {
  375. $checked = '';
  376. }
  377. $admin_url = ( is_plugin_active( 'wp-tweets-pro/wpt-pro-functions.php' ) ) ? admin_url( 'admin.php?page=wp-tweets-pro' ) : admin_url( 'options-general.php?page=wp-to-twitter/wp-to-twitter.php' );
  378. echo "
  379. <form method='post' action='$admin_url'>
  380. <div><input type='hidden' name='_wpnonce' value='" . wp_create_nonce( 'wp-to-twitter-nonce' ) . "' /></div>
  381. <div>
  382. <p>" .
  383. __( "If you're having trouble with WP to Twitter, please try to answer these questions in your message:", 'wp-to-twitter' )
  384. . "</p>
  385. <ul>
  386. <li>" . __( 'What were you doing when the problem occurred?', 'wp-to-twitter' ) . "</li>
  387. <li>" . __( 'What did you expect to happen?', 'wp-to-twitter' ) . "</li>
  388. <li>" . __( 'What happened instead?', 'wp-to-twitter' ) . "</li>
  389. </ul>
  390. <p>
  391. <code>" . __( 'Reply to:', 'wp-to-twitter' ) . " \"$current_user->display_name\" &lt;$current_user->user_email&gt;</code>
  392. </p>
  393. <p>
  394. <input type='checkbox' name='has_read_faq' id='has_read_faq' value='on' required='required' aria-required='true' /> <label for='has_read_faq'>" . sprintf( __( 'I have read <a href="%1$s">the FAQ for this plug-in</a> <span>(required)</span>', 'wp-to-twitter' ), 'http://www.joedolson.com/wp-to-twitter/support-2/' ) . "
  395. </p>
  396. <p>
  397. <input type='checkbox' name='has_donated' id='has_donated' value='on' $checked /> <label for='has_donated'>" . sprintf( __( 'I have <a href="%1$s">made a donation to help support this plug-in</a>', 'wp-to-twitter' ), 'http://www.joedolson.com/donate.php' ) . "</label>
  398. </p>
  399. <p>
  400. <label for='support_request'>" . __( 'Support Request:', 'wp-to-twitter' ) . "</label><br /><textarea class='support-request' name='support_request' id='support_request' cols='80' rows='10'>" . stripslashes( esc_attr( $request ) ) . "</textarea>
  401. </p>
  402. <p>
  403. <input type='submit' value='" . __( 'Send Support Request', 'wp-to-twitter' ) . "' name='wpt_support' class='button-primary' />
  404. </p>
  405. <p>" .
  406. __( 'The following additional information will be sent with your support request:', 'wp-to-twitter' )
  407. . "</p>
  408. <div class='mc_support'>
  409. " . wpautop( $data ) . "
  410. </div>
  411. </div>
  412. </form>";
  413. }
  414. function wpt_is_writable( $file ) {
  415. if ( function_exists( 'wp_is_writable' ) ) {
  416. $is_writable = wp_is_writable( $file );
  417. } else {
  418. $is_writable = is_writeable( $file );
  419. }
  420. return $is_writable;
  421. }
  422. /**
  423. * Normalizer is a PHP fallback implementation of the Normalizer class provided by the intl extension.
  424. *
  425. * It has been validated with Unicode 6.1 Normalization Conformance Test.
  426. * See http://www.unicode.org/reports/tr15/ for detailed info about Unicode normalizations.
  427. */
  428. class WPT_Normalizer
  429. {
  430. const
  431. NONE = 1,
  432. FORM_D = 2, NFD = 2,
  433. FORM_KD = 3, NFKD = 3,
  434. FORM_C = 4, NFC = 4,
  435. FORM_KC = 5, NFKC = 5;
  436. protected static
  437. $C, $D, $KD, $cC,
  438. $ulen_mask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4),
  439. $ASCII = "\x20\x65\x69\x61\x73\x6E\x74\x72\x6F\x6C\x75\x64\x5D\x5B\x63\x6D\x70\x27\x0A\x67\x7C\x68\x76\x2E\x66\x62\x2C\x3A\x3D\x2D\x71\x31\x30\x43\x32\x2A\x79\x78\x29\x28\x4C\x39\x41\x53\x2F\x50\x22\x45\x6A\x4D\x49\x6B\x33\x3E\x35\x54\x3C\x44\x34\x7D\x42\x7B\x38\x46\x77\x52\x36\x37\x55\x47\x4E\x3B\x4A\x7A\x56\x23\x48\x4F\x57\x5F\x26\x21\x4B\x3F\x58\x51\x25\x59\x5C\x09\x5A\x2B\x7E\x5E\x24\x40\x60\x7F\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F";
  440. static function isNormalized($s, $form = self::NFC)
  441. {
  442. if (strspn($s, self::$ASCII) === strlen($s)) return true;
  443. if (self::NFC === $form && preg_match('//u', $s) && !preg_match('/[^\x00-\x{2FF}]/u', $s)) return true;
  444. return false; // Pretend false as quick checks implementented in PHP won't be so quick
  445. }
  446. static function normalize($s, $form = self::NFC)
  447. {
  448. if (!preg_match('//u', $s)) return false;
  449. switch ($form)
  450. {
  451. case self::NONE: return $s;
  452. case self::NFC: $C = true; $K = false; break;
  453. case self::NFD: $C = false; $K = false; break;
  454. case self::NFKC: $C = true; $K = true; break;
  455. case self::NFKD: $C = false; $K = true; break;
  456. default: return false;
  457. }
  458. if (!strlen($s)) return '';
  459. if ($K && empty(self::$KD)) self::$KD = self::getData('compatibilityDecomposition');
  460. if (empty(self::$D))
  461. {
  462. self::$D = self::getData('canonicalDecomposition');
  463. self::$cC = self::getData('combiningClass');
  464. }
  465. if ($C)
  466. {
  467. if (empty(self::$C)) self::$C = self::getData('canonicalComposition');
  468. return self::recompose(self::decompose($s, $K));
  469. }
  470. else return self::decompose($s, $K);
  471. }
  472. protected static function recompose($s)
  473. {
  474. $ASCII = self::$ASCII;
  475. $compMap = self::$C;
  476. $combClass = self::$cC;
  477. $ulen_mask = self::$ulen_mask;
  478. $result = $tail = '';
  479. $i = $s[0] < "\x80" ? 1 : $ulen_mask[$s[0] & "\xF0"];
  480. $len = strlen($s);
  481. $last_uchr = substr($s, 0, $i);
  482. $last_ucls = isset($combClass[$last_uchr]) ? 256 : 0;
  483. while ($i < $len)
  484. {
  485. if ($s[$i] < "\x80")
  486. {
  487. // ASCII chars
  488. if ($tail)
  489. {
  490. $last_uchr .= $tail;
  491. $tail = '';
  492. }
  493. if ($j = strspn($s, $ASCII, $i+1))
  494. {
  495. $last_uchr .= substr($s, $i, $j);
  496. $i += $j;
  497. }
  498. $result .= $last_uchr;
  499. $last_uchr = $s[$i];
  500. ++$i;
  501. }
  502. else
  503. {
  504. $ulen = $ulen_mask[$s[$i] & "\xF0"];
  505. $uchr = substr($s, $i, $ulen);
  506. if ($last_uchr < "\xE1\x84\x80" || "\xE1\x84\x92" < $last_uchr
  507. || $uchr < "\xE1\x85\xA1" || "\xE1\x85\xB5" < $uchr
  508. || $last_ucls)
  509. {
  510. // Table lookup and combining chars composition
  511. $ucls = isset($combClass[$uchr]) ? $combClass[$uchr] : 0;
  512. if (isset($compMap[$last_uchr . $uchr]) && (!$last_ucls || $last_ucls < $ucls))
  513. {
  514. $last_uchr = $compMap[$last_uchr . $uchr];
  515. }
  516. else if ($last_ucls = $ucls) $tail .= $uchr;
  517. else
  518. {
  519. if ($tail)
  520. {
  521. $last_uchr .= $tail;
  522. $tail = '';
  523. }
  524. $result .= $last_uchr;
  525. $last_uchr = $uchr;
  526. }
  527. }
  528. else
  529. {
  530. // Hangul chars
  531. $L = ord($last_uchr[2]) - 0x80;
  532. $V = ord($uchr[2]) - 0xA1;
  533. $T = 0;
  534. $uchr = substr($s, $i + $ulen, 3);
  535. if ("\xE1\x86\xA7" <= $uchr && $uchr <= "\xE1\x87\x82")
  536. {
  537. $T = ord($uchr[2]) - 0xA7;
  538. 0 > $T && $T += 0x40;
  539. $ulen += 3;
  540. }
  541. $L = 0xAC00 + ($L * 21 + $V) * 28 + $T;
  542. $last_uchr = chr(0xE0 | $L>>12) . chr(0x80 | $L>>6 & 0x3F) . chr(0x80 | $L & 0x3F);
  543. }
  544. $i += $ulen;
  545. }
  546. }
  547. return $result . $last_uchr . $tail;
  548. }
  549. protected static function decompose($s, $c)
  550. {
  551. $result = '';
  552. $ASCII = self::$ASCII;
  553. $decompMap = self::$D;
  554. $combClass = self::$cC;
  555. $ulen_mask = self::$ulen_mask;
  556. if ($c) $compatMap = self::$KD;
  557. $c = array();
  558. $i = 0;
  559. $len = strlen($s);
  560. while ($i < $len)
  561. {
  562. if ($s[$i] < "\x80")
  563. {
  564. // ASCII chars
  565. if ($c)
  566. {
  567. ksort($c);
  568. $result .= implode('', $c);
  569. $c = array();
  570. }
  571. $j = 1 + strspn($s, $ASCII, $i+1);
  572. $result .= substr($s, $i, $j);
  573. $i += $j;
  574. }
  575. else
  576. {
  577. $ulen = $ulen_mask[$s[$i] & "\xF0"];
  578. $uchr = substr($s, $i, $ulen);
  579. $i += $ulen;
  580. if (isset($combClass[$uchr]))
  581. {
  582. // Combining chars, for sorting
  583. isset($c[$combClass[$uchr]]) || $c[$combClass[$uchr]] = '';
  584. $c[$combClass[$uchr]] .= isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$uchr]) ? $decompMap[$uchr] : $uchr);
  585. }
  586. else
  587. {
  588. if ($c)
  589. {
  590. ksort($c);
  591. $result .= implode('', $c);
  592. $c = array();
  593. }
  594. if ($uchr < "\xEA\xB0\x80" || "\xED\x9E\xA3" < $uchr)
  595. {
  596. // Table lookup
  597. $j = isset($compatMap[$uchr]) ? $compatMap[$uchr] : (isset($decompMap[$uchr]) ? $decompMap[$uchr] : $uchr);
  598. if ($uchr != $j)
  599. {
  600. $uchr = $j;
  601. $j = strlen($uchr);
  602. $ulen = $uchr[0] < "\x80" ? 1 : $ulen_mask[$uchr[0] & "\xF0"];
  603. if ($ulen != $j)
  604. {
  605. // Put trailing chars in $s
  606. $j -= $ulen;
  607. $i -= $j;
  608. if (0 > $i)
  609. {
  610. $s = str_repeat(' ', -$i) . $s;
  611. $len -= $i;
  612. $i = 0;
  613. }
  614. while ($j--) $s[$i+$j] = $uchr[$ulen+$j];
  615. $uchr = substr($uchr, 0, $ulen);
  616. }
  617. }
  618. }
  619. else
  620. {
  621. // Hangul chars
  622. $uchr = unpack('C*', $uchr);
  623. $j = (($uchr[1]-224) << 12) + (($uchr[2]-128) << 6) + $uchr[3] - 0xAC80;
  624. $uchr = "\xE1\x84" . chr(0x80 + (int) ($j / 588))
  625. . "\xE1\x85" . chr(0xA1 + (int) (($j % 588) / 28));
  626. if ($j %= 28)
  627. {
  628. $uchr .= $j < 25
  629. ? ("\xE1\x86" . chr(0xA7 + $j))
  630. : ("\xE1\x87" . chr(0x67 + $j));
  631. }
  632. }
  633. $result .= $uchr;
  634. }
  635. }
  636. }
  637. if ($c)
  638. {
  639. ksort($c);
  640. $result .= implode('', $c);
  641. }
  642. return $result;
  643. }
  644. protected static function getData($file)
  645. {
  646. $file = __DIR__ . '/unidata/' . $file . '.ser';
  647. if (file_exists($file)) return unserialize(file_get_contents($file));
  648. else return false;
  649. }
  650. }