PageRenderTime 64ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/includes/functions.php

https://github.com/cicorias/YOURLS
PHP | 2244 lines | 1274 code | 322 blank | 648 comment | 299 complexity | 6360b8c5338926f39a56dec8d41edc4f MD5 | raw file
Possible License(s): LGPL-2.0
  1. <?php
  2. /*
  3. * YOURLS
  4. * Function library
  5. */
  6. /**
  7. * Determine the allowed character set in short URLs
  8. *
  9. */
  10. function yourls_get_shorturl_charset() {
  11. static $charset = null;
  12. if( $charset !== null )
  13. return $charset;
  14. if( defined('YOURLS_URL_CONVERT') && in_array( YOURLS_URL_CONVERT, array( 62, 64 ) ) ) {
  15. $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  16. } else {
  17. // defined to 36, or wrongly defined
  18. $charset = '0123456789abcdefghijklmnopqrstuvwxyz';
  19. }
  20. $charset = yourls_apply_filter( 'get_shorturl_charset', $charset );
  21. return $charset;
  22. }
  23. /**
  24. * Make an optimized regexp pattern from a string of characters
  25. *
  26. */
  27. function yourls_make_regexp_pattern( $string ) {
  28. $pattern = preg_quote( $string, '-' ); // add - as an escaped characters -- this is fixed in PHP 5.3
  29. // TODO: replace char sequences by smart sequences such as 0-9, a-z, A-Z ... ?
  30. return $pattern;
  31. }
  32. /**
  33. * Is a URL a short URL? Accept either 'http://sho.rt/abc' or 'abc'
  34. *
  35. */
  36. function yourls_is_shorturl( $shorturl ) {
  37. // TODO: make sure this function evolves with the feature set.
  38. $is_short = false;
  39. // Is $shorturl a URL (http://sho.rt/abc) or a keyword (abc) ?
  40. if( yourls_get_protocol( $shorturl ) ) {
  41. $keyword = yourls_get_relative_url( $shorturl );
  42. } else {
  43. $keyword = $shorturl;
  44. }
  45. // Check if it's a valid && used keyword
  46. if( $keyword && $keyword == yourls_sanitize_string( $keyword ) && yourls_keyword_is_taken( $keyword ) ) {
  47. $is_short = true;
  48. }
  49. return yourls_apply_filter( 'is_shorturl', $is_short, $shorturl );
  50. }
  51. /**
  52. * Check to see if a given keyword is reserved (ie reserved URL or an existing page). Returns bool
  53. *
  54. */
  55. function yourls_keyword_is_reserved( $keyword ) {
  56. global $yourls_reserved_URL;
  57. $keyword = yourls_sanitize_keyword( $keyword );
  58. $reserved = false;
  59. if ( in_array( $keyword, $yourls_reserved_URL)
  60. or file_exists( YOURLS_ABSPATH ."/pages/$keyword.php" )
  61. or is_dir( YOURLS_ABSPATH ."/$keyword" )
  62. )
  63. $reserved = true;
  64. return yourls_apply_filter( 'keyword_is_reserved', $reserved, $keyword );
  65. }
  66. /**
  67. * Function: Get client IP Address. Returns a DB safe string.
  68. *
  69. */
  70. function yourls_get_IP() {
  71. $ip = '';
  72. // Precedence: if set, X-Forwarded-For > HTTP_X_FORWARDED_FOR > HTTP_CLIENT_IP > HTTP_VIA > REMOTE_ADDR
  73. $headers = array( 'X-Forwarded-For', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_VIA', 'REMOTE_ADDR' );
  74. foreach( $headers as $header ) {
  75. if ( !empty( $_SERVER[ $header ] ) ) {
  76. $ip = $_SERVER[ $header ];
  77. break;
  78. }
  79. }
  80. // headers can contain multiple IPs (X-Forwarded-For = client, proxy1, proxy2). Take first one.
  81. if ( strpos( $ip, ',' ) !== false )
  82. $ip = substr( $ip, 0, strpos( $ip, ',' ) );
  83. return yourls_apply_filter( 'get_IP', yourls_sanitize_ip( $ip ) );
  84. }
  85. /**
  86. * Get next id a new link will have if no custom keyword provided
  87. *
  88. */
  89. function yourls_get_next_decimal() {
  90. return yourls_apply_filter( 'get_next_decimal', (int)yourls_get_option( 'next_id' ) );
  91. }
  92. /**
  93. * Update id for next link with no custom keyword
  94. *
  95. */
  96. function yourls_update_next_decimal( $int = '' ) {
  97. $int = ( $int == '' ) ? yourls_get_next_decimal() + 1 : (int)$int ;
  98. $update = yourls_update_option( 'next_id', $int );
  99. yourls_do_action( 'update_next_decimal', $int, $update );
  100. return $update;
  101. }
  102. /**
  103. * Delete a link in the DB
  104. *
  105. */
  106. function yourls_delete_link_by_keyword( $keyword ) {
  107. // Allow plugins to short-circuit the whole function
  108. $pre = yourls_apply_filter( 'shunt_delete_link_by_keyword', null, $keyword );
  109. if ( null !== $pre )
  110. return $pre;
  111. global $ydb;
  112. $table = YOURLS_DB_TABLE_URL;
  113. $keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
  114. $delete = $ydb->query("DELETE FROM `$table` WHERE `keyword` = '$keyword';");
  115. yourls_do_action( 'delete_link', $keyword, $delete );
  116. return $delete;
  117. }
  118. /**
  119. * SQL query to insert a new link in the DB. Returns boolean for success or failure of the inserting
  120. *
  121. */
  122. function yourls_insert_link_in_db( $url, $keyword, $title = '' ) {
  123. global $ydb;
  124. $url = yourls_escape( yourls_sanitize_url( $url ) );
  125. $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
  126. $title = yourls_escape( yourls_sanitize_title( $title ) );
  127. $table = YOURLS_DB_TABLE_URL;
  128. $timestamp = date('Y-m-d H:i:s');
  129. $ip = yourls_get_IP();
  130. $insert = $ydb->query("INSERT INTO `$table` (`keyword`, `url`, `title`, `timestamp`, `ip`, `clicks`) VALUES('$keyword', '$url', '$title', '$timestamp', '$ip', 0);");
  131. yourls_do_action( 'insert_link', (bool)$insert, $url, $keyword, $title, $timestamp, $ip );
  132. return (bool)$insert;
  133. }
  134. /**
  135. * Check if a URL already exists in the DB. Return NULL (doesn't exist) or an object with URL informations.
  136. *
  137. */
  138. function yourls_url_exists( $url ) {
  139. // Allow plugins to short-circuit the whole function
  140. $pre = yourls_apply_filter( 'shunt_url_exists', false, $url );
  141. if ( false !== $pre )
  142. return $pre;
  143. global $ydb;
  144. $table = YOURLS_DB_TABLE_URL;
  145. $url = yourls_escape( yourls_sanitize_url( $url) );
  146. $url_exists = $ydb->get_row( "SELECT * FROM `$table` WHERE `url` = '".$url."';" );
  147. return yourls_apply_filter( 'url_exists', $url_exists, $url );
  148. }
  149. /**
  150. * Add a new link in the DB, either with custom keyword, or find one
  151. *
  152. */
  153. function yourls_add_new_link( $url, $keyword = '', $title = '' ) {
  154. // Allow plugins to short-circuit the whole function
  155. $pre = yourls_apply_filter( 'shunt_add_new_link', false, $url, $keyword, $title );
  156. if ( false !== $pre )
  157. return $pre;
  158. $url = yourls_encodeURI( $url );
  159. $url = yourls_escape( yourls_sanitize_url( $url ) );
  160. if ( !$url || $url == 'http://' || $url == 'https://' ) {
  161. $return['status'] = 'fail';
  162. $return['code'] = 'error:nourl';
  163. $return['message'] = yourls__( 'Missing or malformed URL' );
  164. $return['errorCode'] = '400';
  165. return yourls_apply_filter( 'add_new_link_fail_nourl', $return, $url, $keyword, $title );
  166. }
  167. // Prevent DB flood
  168. $ip = yourls_get_IP();
  169. yourls_check_IP_flood( $ip );
  170. // Prevent internal redirection loops: cannot shorten a shortened URL
  171. if( yourls_get_relative_url( $url ) ) {
  172. if( yourls_is_shorturl( $url ) ) {
  173. $return['status'] = 'fail';
  174. $return['code'] = 'error:noloop';
  175. $return['message'] = yourls__( 'URL is a short URL' );
  176. $return['errorCode'] = '400';
  177. return yourls_apply_filter( 'add_new_link_fail_noloop', $return, $url, $keyword, $title );
  178. }
  179. }
  180. yourls_do_action( 'pre_add_new_link', $url, $keyword, $title );
  181. $strip_url = stripslashes( $url );
  182. $return = array();
  183. // duplicates allowed or new URL => store it
  184. if( yourls_allow_duplicate_longurls() || !( $url_exists = yourls_url_exists( $url ) ) ) {
  185. if( isset( $title ) && !empty( $title ) ) {
  186. $title = yourls_sanitize_title( $title );
  187. } else {
  188. $title = yourls_get_remote_title( $url );
  189. }
  190. $title = yourls_apply_filter( 'add_new_title', $title, $url, $keyword );
  191. // Custom keyword provided
  192. if ( $keyword ) {
  193. yourls_do_action( 'add_new_link_custom_keyword', $url, $keyword, $title );
  194. $keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
  195. $keyword = yourls_apply_filter( 'custom_keyword', $keyword, $url, $title );
  196. if ( !yourls_keyword_is_free( $keyword ) ) {
  197. // This shorturl either reserved or taken already
  198. $return['status'] = 'fail';
  199. $return['code'] = 'error:keyword';
  200. $return['message'] = yourls_s( 'Short URL %s already exists in database or is reserved', $keyword );
  201. } else {
  202. // all clear, store !
  203. yourls_insert_link_in_db( $url, $keyword, $title );
  204. $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => date('Y-m-d H:i:s'), 'ip' => $ip );
  205. $return['status'] = 'success';
  206. $return['message'] = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) );
  207. $return['title'] = $title;
  208. $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
  209. $return['shorturl'] = YOURLS_SITE .'/'. $keyword;
  210. }
  211. // Create random keyword
  212. } else {
  213. yourls_do_action( 'add_new_link_create_keyword', $url, $keyword, $title );
  214. $timestamp = date( 'Y-m-d H:i:s' );
  215. $id = yourls_get_next_decimal();
  216. $ok = false;
  217. do {
  218. $keyword = yourls_int2string( $id );
  219. $keyword = yourls_apply_filter( 'random_keyword', $keyword, $url, $title );
  220. if ( yourls_keyword_is_free($keyword) ) {
  221. if( @yourls_insert_link_in_db( $url, $keyword, $title ) ){
  222. // everything ok, populate needed vars
  223. $return['url'] = array('keyword' => $keyword, 'url' => $strip_url, 'title' => $title, 'date' => $timestamp, 'ip' => $ip );
  224. $return['status'] = 'success';
  225. $return['message'] = /* //translators: eg "http://someurl/ added to DB" */ yourls_s( '%s added to database', yourls_trim_long_string( $strip_url ) );
  226. $return['title'] = $title;
  227. $return['html'] = yourls_table_add_row( $keyword, $url, $title, $ip, 0, time() );
  228. $return['shorturl'] = YOURLS_SITE .'/'. $keyword;
  229. }else{
  230. // database error, couldnt store result
  231. $return['status'] = 'fail';
  232. $return['code'] = 'error:db';
  233. $return['message'] = yourls_s( 'Error saving url to database' );
  234. }
  235. $ok = true;
  236. }
  237. $id++;
  238. } while ( !$ok );
  239. @yourls_update_next_decimal( $id );
  240. }
  241. // URL was already stored
  242. } else {
  243. yourls_do_action( 'add_new_link_already_stored', $url, $keyword, $title );
  244. $return['status'] = 'fail';
  245. $return['code'] = 'error:url';
  246. $return['url'] = array( 'keyword' => $url_exists->keyword, 'url' => $strip_url, 'title' => $url_exists->title, 'date' => $url_exists->timestamp, 'ip' => $url_exists->ip, 'clicks' => $url_exists->clicks );
  247. $return['message'] = /* //translators: eg "http://someurl/ already exists" */ yourls_s( '%s already exists in database', yourls_trim_long_string( $strip_url ) );
  248. $return['title'] = $url_exists->title;
  249. $return['shorturl'] = YOURLS_SITE .'/'. $url_exists->keyword;
  250. }
  251. yourls_do_action( 'post_add_new_link', $url, $keyword, $title );
  252. $return['statusCode'] = 200; // regardless of result, this is still a valid request
  253. return yourls_apply_filter( 'add_new_link', $return, $url, $keyword, $title );
  254. }
  255. /**
  256. * Edit a link
  257. *
  258. */
  259. function yourls_edit_link( $url, $keyword, $newkeyword='', $title='' ) {
  260. // Allow plugins to short-circuit the whole function
  261. $pre = yourls_apply_filter( 'shunt_edit_link', null, $keyword, $url, $keyword, $newkeyword, $title );
  262. if ( null !== $pre )
  263. return $pre;
  264. global $ydb;
  265. $table = YOURLS_DB_TABLE_URL;
  266. $url = yourls_escape (yourls_sanitize_url( $url ) );
  267. $keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
  268. $title = yourls_escape( yourls_sanitize_title( $title ) );
  269. $newkeyword = yourls_escape( yourls_sanitize_string( $newkeyword ) );
  270. $strip_url = stripslashes( $url );
  271. $strip_title = stripslashes( $title );
  272. $old_url = $ydb->get_var( "SELECT `url` FROM `$table` WHERE `keyword` = '$keyword';" );
  273. // Check if new URL is not here already
  274. if ( $old_url != $url && !yourls_allow_duplicate_longurls() ) {
  275. $new_url_already_there = intval($ydb->get_var("SELECT COUNT(keyword) FROM `$table` WHERE `url` = '$url';"));
  276. } else {
  277. $new_url_already_there = false;
  278. }
  279. // Check if the new keyword is not here already
  280. if ( $newkeyword != $keyword ) {
  281. $keyword_is_ok = yourls_keyword_is_free( $newkeyword );
  282. } else {
  283. $keyword_is_ok = true;
  284. }
  285. yourls_do_action( 'pre_edit_link', $url, $keyword, $newkeyword, $new_url_already_there, $keyword_is_ok );
  286. // All clear, update
  287. if ( ( !$new_url_already_there || yourls_allow_duplicate_longurls() ) && $keyword_is_ok ) {
  288. $update_url = $ydb->query( "UPDATE `$table` SET `url` = '$url', `keyword` = '$newkeyword', `title` = '$title' WHERE `keyword` = '$keyword';" );
  289. if( $update_url ) {
  290. $return['url'] = array( 'keyword' => $newkeyword, 'shorturl' => YOURLS_SITE.'/'.$newkeyword, 'url' => $strip_url, 'display_url' => yourls_trim_long_string( $strip_url ), 'title' => $strip_title, 'display_title' => yourls_trim_long_string( $strip_title ) );
  291. $return['status'] = 'success';
  292. $return['message'] = yourls__( 'Link updated in database' );
  293. } else {
  294. $return['status'] = 'fail';
  295. $return['message'] = /* //translators: "Error updating http://someurl/ (Shorturl: http://sho.rt/blah)" */ yourls_s( 'Error updating %s (Short URL: %s)', yourls_trim_long_string( $strip_url ), $keyword ) ;
  296. }
  297. // Nope
  298. } else {
  299. $return['status'] = 'fail';
  300. $return['message'] = yourls__( 'URL or keyword already exists in database' );
  301. }
  302. return yourls_apply_filter( 'edit_link', $return, $url, $keyword, $newkeyword, $title, $new_url_already_there, $keyword_is_ok );
  303. }
  304. /**
  305. * Update a title link (no checks for duplicates etc..)
  306. *
  307. */
  308. function yourls_edit_link_title( $keyword, $title ) {
  309. // Allow plugins to short-circuit the whole function
  310. $pre = yourls_apply_filter( 'shunt_edit_link_title', null, $keyword, $title );
  311. if ( null !== $pre )
  312. return $pre;
  313. global $ydb;
  314. $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
  315. $title = yourls_escape( yourls_sanitize_title( $title ) );
  316. $table = YOURLS_DB_TABLE_URL;
  317. $update = $ydb->query("UPDATE `$table` SET `title` = '$title' WHERE `keyword` = '$keyword';");
  318. return $update;
  319. }
  320. /**
  321. * Check if keyword id is free (ie not already taken, and not reserved). Return bool.
  322. *
  323. */
  324. function yourls_keyword_is_free( $keyword ) {
  325. $free = true;
  326. if ( yourls_keyword_is_reserved( $keyword ) or yourls_keyword_is_taken( $keyword ) )
  327. $free = false;
  328. return yourls_apply_filter( 'keyword_is_free', $free, $keyword );
  329. }
  330. /**
  331. * Check if a keyword is taken (ie there is already a short URL with this id). Return bool.
  332. *
  333. */
  334. function yourls_keyword_is_taken( $keyword ) {
  335. // Allow plugins to short-circuit the whole function
  336. $pre = yourls_apply_filter( 'shunt_keyword_is_taken', false, $keyword );
  337. if ( false !== $pre )
  338. return $pre;
  339. global $ydb;
  340. $keyword = yourls_escape( yourls_sanitize_keyword( $keyword ) );
  341. $taken = false;
  342. $table = YOURLS_DB_TABLE_URL;
  343. $already_exists = $ydb->get_var( "SELECT COUNT(`keyword`) FROM `$table` WHERE `keyword` = '$keyword';" );
  344. if ( $already_exists )
  345. $taken = true;
  346. return yourls_apply_filter( 'keyword_is_taken', $taken, $keyword );
  347. }
  348. /**
  349. * Return XML output.
  350. *
  351. */
  352. function yourls_xml_encode( $array ) {
  353. require_once( YOURLS_INC.'/functions-xml.php' );
  354. $converter= new yourls_array2xml;
  355. return $converter->array2xml( $array );
  356. }
  357. /**
  358. * Return array of all information associated with keyword. Returns false if keyword not found. Set optional $use_cache to false to force fetching from DB
  359. *
  360. */
  361. function yourls_get_keyword_infos( $keyword, $use_cache = true ) {
  362. global $ydb;
  363. $keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
  364. yourls_do_action( 'pre_get_keyword', $keyword, $use_cache );
  365. if( isset( $ydb->infos[$keyword] ) && $use_cache == true ) {
  366. return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
  367. }
  368. yourls_do_action( 'get_keyword_not_cached', $keyword );
  369. $table = YOURLS_DB_TABLE_URL;
  370. $infos = $ydb->get_row( "SELECT * FROM `$table` WHERE `keyword` = '$keyword'" );
  371. if( $infos ) {
  372. $infos = (array)$infos;
  373. $ydb->infos[ $keyword ] = $infos;
  374. } else {
  375. $ydb->infos[ $keyword ] = false;
  376. }
  377. return yourls_apply_filter( 'get_keyword_infos', $ydb->infos[$keyword], $keyword );
  378. }
  379. /**
  380. * Return (string) selected information associated with a keyword. Optional $notfound = string default message if nothing found
  381. *
  382. */
  383. function yourls_get_keyword_info( $keyword, $field, $notfound = false ) {
  384. // Allow plugins to short-circuit the whole function
  385. $pre = yourls_apply_filter( 'shunt_get_keyword_info', false, $keyword, $field, $notfound );
  386. if ( false !== $pre )
  387. return $pre;
  388. $keyword = yourls_sanitize_string( $keyword );
  389. $infos = yourls_get_keyword_infos( $keyword );
  390. $return = $notfound;
  391. if ( isset( $infos[ $field ] ) && $infos[ $field ] !== false )
  392. $return = $infos[ $field ];
  393. return yourls_apply_filter( 'get_keyword_info', $return, $keyword, $field, $notfound );
  394. }
  395. /**
  396. * Return title associated with keyword. Optional $notfound = string default message if nothing found
  397. *
  398. */
  399. function yourls_get_keyword_title( $keyword, $notfound = false ) {
  400. return yourls_get_keyword_info( $keyword, 'title', $notfound );
  401. }
  402. /**
  403. * Return long URL associated with keyword. Optional $notfound = string default message if nothing found
  404. *
  405. */
  406. function yourls_get_keyword_longurl( $keyword, $notfound = false ) {
  407. return yourls_get_keyword_info( $keyword, 'url', $notfound );
  408. }
  409. /**
  410. * Return number of clicks on a keyword. Optional $notfound = string default message if nothing found
  411. *
  412. */
  413. function yourls_get_keyword_clicks( $keyword, $notfound = false ) {
  414. return yourls_get_keyword_info( $keyword, 'clicks', $notfound );
  415. }
  416. /**
  417. * Return IP that added a keyword. Optional $notfound = string default message if nothing found
  418. *
  419. */
  420. function yourls_get_keyword_IP( $keyword, $notfound = false ) {
  421. return yourls_get_keyword_info( $keyword, 'ip', $notfound );
  422. }
  423. /**
  424. * Return timestamp associated with a keyword. Optional $notfound = string default message if nothing found
  425. *
  426. */
  427. function yourls_get_keyword_timestamp( $keyword, $notfound = false ) {
  428. return yourls_get_keyword_info( $keyword, 'timestamp', $notfound );
  429. }
  430. /**
  431. * Update click count on a short URL. Return 0/1 for error/success.
  432. *
  433. */
  434. function yourls_update_clicks( $keyword, $clicks = false ) {
  435. // Allow plugins to short-circuit the whole function
  436. $pre = yourls_apply_filter( 'shunt_update_clicks', false, $keyword, $clicks );
  437. if ( false !== $pre )
  438. return $pre;
  439. global $ydb;
  440. $keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
  441. $table = YOURLS_DB_TABLE_URL;
  442. if ( $clicks !== false && is_int( $clicks ) && $clicks >= 0 )
  443. $update = $ydb->query( "UPDATE `$table` SET `clicks` = $clicks WHERE `keyword` = '$keyword'" );
  444. else
  445. $update = $ydb->query( "UPDATE `$table` SET `clicks` = clicks + 1 WHERE `keyword` = '$keyword'" );
  446. yourls_do_action( 'update_clicks', $keyword, $update, $clicks );
  447. return $update;
  448. }
  449. /**
  450. * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
  451. *
  452. */
  453. function yourls_get_stats( $filter = 'top', $limit = 10, $start = 0 ) {
  454. global $ydb;
  455. switch( $filter ) {
  456. case 'bottom':
  457. $sort_by = 'clicks';
  458. $sort_order = 'asc';
  459. break;
  460. case 'last':
  461. $sort_by = 'timestamp';
  462. $sort_order = 'desc';
  463. break;
  464. case 'rand':
  465. case 'random':
  466. $sort_by = 'RAND()';
  467. $sort_order = '';
  468. break;
  469. case 'top':
  470. default:
  471. $sort_by = 'clicks';
  472. $sort_order = 'desc';
  473. break;
  474. }
  475. // Fetch links
  476. $limit = intval( $limit );
  477. $start = intval( $start );
  478. if ( $limit > 0 ) {
  479. $table_url = YOURLS_DB_TABLE_URL;
  480. $results = $ydb->get_results( "SELECT * FROM `$table_url` WHERE 1=1 ORDER BY `$sort_by` $sort_order LIMIT $start, $limit;" );
  481. $return = array();
  482. $i = 1;
  483. foreach ( (array)$results as $res ) {
  484. $return['links']['link_'.$i++] = array(
  485. 'shorturl' => YOURLS_SITE .'/'. $res->keyword,
  486. 'url' => $res->url,
  487. 'title' => $res->title,
  488. 'timestamp'=> $res->timestamp,
  489. 'ip' => $res->ip,
  490. 'clicks' => $res->clicks,
  491. );
  492. }
  493. }
  494. $return['stats'] = yourls_get_db_stats();
  495. $return['statusCode'] = 200;
  496. return yourls_apply_filter( 'get_stats', $return, $filter, $limit, $start );
  497. }
  498. /**
  499. * Return array of stats. (string)$filter is 'bottom', 'last', 'rand' or 'top'. (int)$limit is the number of links to return
  500. *
  501. */
  502. function yourls_get_link_stats( $shorturl ) {
  503. global $ydb;
  504. $table_url = YOURLS_DB_TABLE_URL;
  505. $shorturl = yourls_escape( yourls_sanitize_keyword( $shorturl ) );
  506. $res = $ydb->get_row( "SELECT * FROM `$table_url` WHERE keyword = '$shorturl';" );
  507. $return = array();
  508. if( !$res ) {
  509. // non existent link
  510. $return = array(
  511. 'statusCode' => 404,
  512. 'message' => 'Error: short URL not found',
  513. );
  514. } else {
  515. $return = array(
  516. 'statusCode' => 200,
  517. 'message' => 'success',
  518. 'link' => array(
  519. 'shorturl' => YOURLS_SITE .'/'. $res->keyword,
  520. 'url' => $res->url,
  521. 'title' => $res->title,
  522. 'timestamp'=> $res->timestamp,
  523. 'ip' => $res->ip,
  524. 'clicks' => $res->clicks,
  525. )
  526. );
  527. }
  528. return yourls_apply_filter( 'get_link_stats', $return, $shorturl );
  529. }
  530. /**
  531. * Get total number of URLs and sum of clicks. Input: optional "AND WHERE" clause. Returns array
  532. *
  533. * IMPORTANT NOTE: make sure arguments for the $where clause have been sanitized and yourls_escape()'d
  534. * before calling this function.
  535. *
  536. */
  537. function yourls_get_db_stats( $where = '' ) {
  538. global $ydb;
  539. $table_url = YOURLS_DB_TABLE_URL;
  540. $totals = $ydb->get_row( "SELECT COUNT(keyword) as count, SUM(clicks) as sum FROM `$table_url` WHERE 1=1 $where" );
  541. $return = array( 'total_links' => $totals->count, 'total_clicks' => $totals->sum );
  542. return yourls_apply_filter( 'get_db_stats', $return, $where );
  543. }
  544. /**
  545. * Get number of SQL queries performed
  546. *
  547. */
  548. function yourls_get_num_queries() {
  549. global $ydb;
  550. return yourls_apply_filter( 'get_num_queries', $ydb->num_queries );
  551. }
  552. /**
  553. * Returns a sanitized a user agent string. Given what I found on http://www.user-agents.org/ it should be OK.
  554. *
  555. */
  556. function yourls_get_user_agent() {
  557. if ( !isset( $_SERVER['HTTP_USER_AGENT'] ) )
  558. return '-';
  559. $ua = strip_tags( html_entity_decode( $_SERVER['HTTP_USER_AGENT'] ));
  560. $ua = preg_replace('![^0-9a-zA-Z\':., /{}\(\)\[\]\+@&\!\?;_\-=~\*\#]!', '', $ua );
  561. return yourls_apply_filter( 'get_user_agent', substr( $ua, 0, 254 ) );
  562. }
  563. /**
  564. * Redirect to another page
  565. *
  566. */
  567. function yourls_redirect( $location, $code = 301 ) {
  568. yourls_do_action( 'pre_redirect', $location, $code );
  569. $location = yourls_apply_filter( 'redirect_location', $location, $code );
  570. $code = yourls_apply_filter( 'redirect_code', $code, $location );
  571. // Redirect, either properly if possible, or via Javascript otherwise
  572. if( !headers_sent() ) {
  573. yourls_status_header( $code );
  574. header( "Location: $location" );
  575. } else {
  576. yourls_redirect_javascript( $location );
  577. }
  578. die();
  579. }
  580. /**
  581. * Set HTTP status header
  582. *
  583. */
  584. function yourls_status_header( $code = 200 ) {
  585. if( headers_sent() )
  586. return;
  587. $protocol = $_SERVER['SERVER_PROTOCOL'];
  588. if ( 'HTTP/1.1' != $protocol && 'HTTP/1.0' != $protocol )
  589. $protocol = 'HTTP/1.0';
  590. $code = intval( $code );
  591. $desc = yourls_get_HTTP_status( $code );
  592. @header ("$protocol $code $desc"); // This causes problems on IIS and some FastCGI setups
  593. yourls_do_action( 'status_header', $code );
  594. }
  595. /**
  596. * Redirect to another page using Javascript. Set optional (bool)$dontwait to false to force manual redirection (make sure a message has been read by user)
  597. *
  598. */
  599. function yourls_redirect_javascript( $location, $dontwait = true ) {
  600. yourls_do_action( 'pre_redirect_javascript', $location, $dontwait );
  601. $location = yourls_apply_filter( 'redirect_javascript', $location, $dontwait );
  602. if( $dontwait ) {
  603. $message = yourls_s( 'if you are not redirected after 10 seconds, please <a href="%s">click here</a>', $location );
  604. echo <<<REDIR
  605. <script type="text/javascript">
  606. window.location="$location";
  607. </script>
  608. <small>($message)</small>
  609. REDIR;
  610. } else {
  611. echo '<p>' . yourls_s( 'Please <a href="%s">click here</a>', $location ) . '</p>';
  612. }
  613. yourls_do_action( 'post_redirect_javascript', $location );
  614. }
  615. /**
  616. * Return a HTTP status code
  617. *
  618. */
  619. function yourls_get_HTTP_status( $code ) {
  620. $code = intval( $code );
  621. $headers_desc = array(
  622. 100 => 'Continue',
  623. 101 => 'Switching Protocols',
  624. 102 => 'Processing',
  625. 200 => 'OK',
  626. 201 => 'Created',
  627. 202 => 'Accepted',
  628. 203 => 'Non-Authoritative Information',
  629. 204 => 'No Content',
  630. 205 => 'Reset Content',
  631. 206 => 'Partial Content',
  632. 207 => 'Multi-Status',
  633. 226 => 'IM Used',
  634. 300 => 'Multiple Choices',
  635. 301 => 'Moved Permanently',
  636. 302 => 'Found',
  637. 303 => 'See Other',
  638. 304 => 'Not Modified',
  639. 305 => 'Use Proxy',
  640. 306 => 'Reserved',
  641. 307 => 'Temporary Redirect',
  642. 400 => 'Bad Request',
  643. 401 => 'Unauthorized',
  644. 402 => 'Payment Required',
  645. 403 => 'Forbidden',
  646. 404 => 'Not Found',
  647. 405 => 'Method Not Allowed',
  648. 406 => 'Not Acceptable',
  649. 407 => 'Proxy Authentication Required',
  650. 408 => 'Request Timeout',
  651. 409 => 'Conflict',
  652. 410 => 'Gone',
  653. 411 => 'Length Required',
  654. 412 => 'Precondition Failed',
  655. 413 => 'Request Entity Too Large',
  656. 414 => 'Request-URI Too Long',
  657. 415 => 'Unsupported Media Type',
  658. 416 => 'Requested Range Not Satisfiable',
  659. 417 => 'Expectation Failed',
  660. 422 => 'Unprocessable Entity',
  661. 423 => 'Locked',
  662. 424 => 'Failed Dependency',
  663. 426 => 'Upgrade Required',
  664. 500 => 'Internal Server Error',
  665. 501 => 'Not Implemented',
  666. 502 => 'Bad Gateway',
  667. 503 => 'Service Unavailable',
  668. 504 => 'Gateway Timeout',
  669. 505 => 'HTTP Version Not Supported',
  670. 506 => 'Variant Also Negotiates',
  671. 507 => 'Insufficient Storage',
  672. 510 => 'Not Extended'
  673. );
  674. if ( isset( $headers_desc[$code] ) )
  675. return $headers_desc[$code];
  676. else
  677. return '';
  678. }
  679. /**
  680. * Log a redirect (for stats)
  681. *
  682. * This function does not check for the existence of a valid keyword, in order to save a query. Make sure the keyword
  683. * exists before calling it.
  684. *
  685. * @since 1.4
  686. * @param string $keyword short URL keyword
  687. * @return mixed Result of the INSERT query (1 on success)
  688. */
  689. function yourls_log_redirect( $keyword ) {
  690. // Allow plugins to short-circuit the whole function
  691. $pre = yourls_apply_filter( 'shunt_log_redirect', false, $keyword );
  692. if ( false !== $pre )
  693. return $pre;
  694. if ( !yourls_do_log_redirect() )
  695. return true;
  696. global $ydb;
  697. $table = YOURLS_DB_TABLE_LOG;
  698. $keyword = yourls_escape( yourls_sanitize_string( $keyword ) );
  699. $referrer = ( isset( $_SERVER['HTTP_REFERER'] ) ? yourls_escape( yourls_sanitize_url( $_SERVER['HTTP_REFERER'] ) ) : 'direct' );
  700. $ua = yourls_escape( yourls_get_user_agent() );
  701. $ip = yourls_escape( yourls_get_IP() );
  702. $location = yourls_escape( yourls_geo_ip_to_countrycode( $ip ) );
  703. return $ydb->query( "INSERT INTO `$table` (click_time, shorturl, referrer, user_agent, ip_address, country_code) VALUES (NOW(), '$keyword', '$referrer', '$ua', '$ip', '$location')" );
  704. }
  705. /**
  706. * Check if we want to not log redirects (for stats)
  707. *
  708. */
  709. function yourls_do_log_redirect() {
  710. return ( !defined( 'YOURLS_NOSTATS' ) || YOURLS_NOSTATS != true );
  711. }
  712. /**
  713. * Converts an IP to a 2 letter country code, using GeoIP database if available in includes/geo/
  714. *
  715. * @since 1.4
  716. * @param string $ip IP or, if empty string, will be current user IP
  717. * @param string $defaut Default string to return if IP doesn't resolve to a country (malformed, private IP...)
  718. * @return string 2 letter country code (eg 'US') or $default
  719. */
  720. function yourls_geo_ip_to_countrycode( $ip = '', $default = '' ) {
  721. // Allow plugins to short-circuit the Geo IP API
  722. $location = yourls_apply_filter( 'shunt_geo_ip_to_countrycode', false, $ip, $default ); // at this point $ip can be '', check if your plugin hooks in here
  723. if ( false !== $location )
  724. return $location;
  725. if ( $ip == '' )
  726. $ip = yourls_get_IP();
  727. // Use IPv4 or IPv6 DB & functions
  728. if( false === filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) {
  729. $db = 'GeoIP.dat';
  730. $func = 'geoip_country_code_by_addr';
  731. } else {
  732. $db = 'GeoIPv6.dat';
  733. $func = 'geoip_country_code_by_addr_v6';
  734. }
  735. if ( !file_exists( YOURLS_INC . '/geo/' . $db ) || !file_exists( YOURLS_INC .'/geo/geoip.inc' ) )
  736. return $default;
  737. require_once( YOURLS_INC . '/geo/geoip.inc' );
  738. $gi = geoip_open( YOURLS_INC . '/geo/' . $db, GEOIP_STANDARD );
  739. try {
  740. $location = call_user_func( $func, $gi, $ip );
  741. } catch ( Exception $e ) {
  742. $location = '';
  743. }
  744. geoip_close( $gi );
  745. if( '' == $location )
  746. $location = $default;
  747. return yourls_apply_filter( 'geo_ip_to_countrycode', $location, $ip, $default );
  748. }
  749. /**
  750. * Converts a 2 letter country code to long name (ie AU -> Australia)
  751. *
  752. */
  753. function yourls_geo_countrycode_to_countryname( $code ) {
  754. // Allow plugins to short-circuit the Geo IP API
  755. $country = yourls_apply_filter( 'shunt_geo_countrycode_to_countryname', false, $code );
  756. if ( false !== $country )
  757. return $country;
  758. // Load the Geo class if not already done
  759. if( !class_exists( 'GeoIP', false ) ) {
  760. $temp = yourls_geo_ip_to_countrycode( '127.0.0.1' );
  761. }
  762. if( class_exists( 'GeoIP', false ) ) {
  763. $geo = new GeoIP;
  764. $id = $geo->GEOIP_COUNTRY_CODE_TO_NUMBER[ $code ];
  765. $long = $geo->GEOIP_COUNTRY_NAMES[ $id ];
  766. return $long;
  767. } else {
  768. return false;
  769. }
  770. }
  771. /**
  772. * Return flag URL from 2 letter country code
  773. *
  774. */
  775. function yourls_geo_get_flag( $code ) {
  776. if( file_exists( YOURLS_INC.'/geo/flags/flag_'.strtolower($code).'.gif' ) ) {
  777. $img = yourls_match_current_protocol( YOURLS_SITE.'/includes/geo/flags/flag_'.( strtolower( $code ) ).'.gif' );
  778. } else {
  779. $img = false;
  780. }
  781. return yourls_apply_filter( 'geo_get_flag', $img, $code );
  782. }
  783. /**
  784. * Check if an upgrade is needed
  785. *
  786. */
  787. function yourls_upgrade_is_needed() {
  788. // check YOURLS_DB_VERSION exist && match values stored in YOURLS_DB_TABLE_OPTIONS
  789. list( $currentver, $currentsql ) = yourls_get_current_version_from_sql();
  790. if( $currentsql < YOURLS_DB_VERSION )
  791. return true;
  792. return false;
  793. }
  794. /**
  795. * Get current version & db version as stored in the options DB. Prior to 1.4 there's no option table.
  796. *
  797. */
  798. function yourls_get_current_version_from_sql() {
  799. $currentver = yourls_get_option( 'version' );
  800. $currentsql = yourls_get_option( 'db_version' );
  801. // Values if version is 1.3
  802. if( !$currentver )
  803. $currentver = '1.3';
  804. if( !$currentsql )
  805. $currentsql = '100';
  806. return array( $currentver, $currentsql);
  807. }
  808. /**
  809. * Read an option from DB (or from cache if available). Return value or $default if not found
  810. *
  811. * Pretty much stolen from WordPress
  812. *
  813. * @since 1.4
  814. * @param string $option Option name. Expected to not be SQL-escaped.
  815. * @param mixed $default Optional value to return if option doesn't exist. Default false.
  816. * @return mixed Value set for the option.
  817. */
  818. function yourls_get_option( $option_name, $default = false ) {
  819. global $ydb;
  820. // Allow plugins to short-circuit options
  821. $pre = yourls_apply_filter( 'shunt_option_'.$option_name, false );
  822. if ( false !== $pre )
  823. return $pre;
  824. // If option not cached already, get its value from the DB
  825. if ( !isset( $ydb->option[$option_name] ) ) {
  826. $table = YOURLS_DB_TABLE_OPTIONS;
  827. $option_name = yourls_escape( $option_name );
  828. $row = $ydb->get_row( "SELECT `option_value` FROM `$table` WHERE `option_name` = '$option_name' LIMIT 1" );
  829. if ( is_object( $row) ) { // Has to be get_row instead of get_var because of funkiness with 0, false, null values
  830. $value = $row->option_value;
  831. } else { // option does not exist, so we must cache its non-existence
  832. $value = $default;
  833. }
  834. $ydb->option[ $option_name ] = yourls_maybe_unserialize( $value );
  835. }
  836. return yourls_apply_filter( 'get_option_'.$option_name, $ydb->option[$option_name] );
  837. }
  838. /**
  839. * Read all options from DB at once
  840. *
  841. * The goal is to read all options at once and then populate array $ydb->option, to prevent further
  842. * SQL queries if we need to read an option value later.
  843. * It's also a simple check whether YOURLS is installed or not (no option = assuming not installed) after
  844. * a check for DB server reachability has been performed
  845. *
  846. * @since 1.4
  847. */
  848. function yourls_get_all_options() {
  849. global $ydb;
  850. // Allow plugins to short-circuit all options. (Note: regular plugins are loaded after all options)
  851. $pre = yourls_apply_filter( 'shunt_all_options', false );
  852. if ( false !== $pre )
  853. return $pre;
  854. $table = YOURLS_DB_TABLE_OPTIONS;
  855. $allopt = $ydb->get_results( "SELECT `option_name`, `option_value` FROM `$table` WHERE 1=1" );
  856. foreach( (array)$allopt as $option ) {
  857. $ydb->option[ $option->option_name ] = yourls_maybe_unserialize( $option->option_value );
  858. }
  859. if( property_exists( $ydb, 'option' ) ) {
  860. $ydb->option = yourls_apply_filter( 'get_all_options', $ydb->option );
  861. $ydb->installed = true;
  862. } else {
  863. // Zero option found: either YOURLS is not installed or DB server is dead
  864. if( !yourls_is_db_alive() ) {
  865. yourls_db_dead(); // YOURLS will die here
  866. }
  867. $ydb->installed = false;
  868. }
  869. }
  870. /**
  871. * Update (add if doesn't exist) an option to DB
  872. *
  873. * Pretty much stolen from WordPress
  874. *
  875. * @since 1.4
  876. * @param string $option Option name. Expected to not be SQL-escaped.
  877. * @param mixed $newvalue Option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
  878. * @return bool False if value was not updated, true otherwise.
  879. */
  880. function yourls_update_option( $option_name, $newvalue ) {
  881. global $ydb;
  882. $table = YOURLS_DB_TABLE_OPTIONS;
  883. $option_name = trim( $option_name );
  884. if ( empty( $option_name ) )
  885. return false;
  886. // Use clone to break object refs -- see commit 09b989d375bac65e692277f61a84fede2fb04ae3
  887. if ( is_object( $newvalue ) )
  888. $newvalue = clone $newvalue;
  889. $option_name = yourls_escape( $option_name );
  890. $oldvalue = yourls_get_option( $option_name );
  891. // If the new and old values are the same, no need to update.
  892. if ( $newvalue === $oldvalue )
  893. return false;
  894. if ( false === $oldvalue ) {
  895. yourls_add_option( $option_name, $newvalue );
  896. return true;
  897. }
  898. $_newvalue = yourls_escape( yourls_maybe_serialize( $newvalue ) );
  899. yourls_do_action( 'update_option', $option_name, $oldvalue, $newvalue );
  900. $ydb->query( "UPDATE `$table` SET `option_value` = '$_newvalue' WHERE `option_name` = '$option_name'" );
  901. if ( $ydb->rows_affected == 1 ) {
  902. $ydb->option[ $option_name ] = $newvalue;
  903. return true;
  904. }
  905. return false;
  906. }
  907. /**
  908. * Add an option to the DB
  909. *
  910. * Pretty much stolen from WordPress
  911. *
  912. * @since 1.4
  913. * @param string $option Name of option to add. Expected to not be SQL-escaped.
  914. * @param mixed $value Optional option value. Must be serializable if non-scalar. Expected to not be SQL-escaped.
  915. * @return bool False if option was not added and true otherwise.
  916. */
  917. function yourls_add_option( $name, $value = '' ) {
  918. global $ydb;
  919. $table = YOURLS_DB_TABLE_OPTIONS;
  920. $name = trim( $name );
  921. if ( empty( $name ) )
  922. return false;
  923. // Use clone to break object refs -- see commit 09b989d375bac65e692277f61a84fede2fb04ae3
  924. if ( is_object( $value ) )
  925. $value = clone $value;
  926. $name = yourls_escape( $name );
  927. // Make sure the option doesn't already exist
  928. if ( false !== yourls_get_option( $name ) )
  929. return false;
  930. $_value = yourls_escape( yourls_maybe_serialize( $value ) );
  931. yourls_do_action( 'add_option', $name, $_value );
  932. $ydb->query( "INSERT INTO `$table` (`option_name`, `option_value`) VALUES ('$name', '$_value')" );
  933. $ydb->option[ $name ] = $value;
  934. return true;
  935. }
  936. /**
  937. * Delete an option from the DB
  938. *
  939. * Pretty much stolen from WordPress
  940. *
  941. * @since 1.4
  942. * @param string $option Option name to delete. Expected to not be SQL-escaped.
  943. * @return bool True, if option is successfully deleted. False on failure.
  944. */
  945. function yourls_delete_option( $name ) {
  946. global $ydb;
  947. $table = YOURLS_DB_TABLE_OPTIONS;
  948. $name = yourls_escape( $name );
  949. // Get the ID, if no ID then return
  950. $option = $ydb->get_row( "SELECT option_id FROM `$table` WHERE `option_name` = '$name'" );
  951. if ( is_null( $option ) || !$option->option_id )
  952. return false;
  953. yourls_do_action( 'delete_option', $name );
  954. $ydb->query( "DELETE FROM `$table` WHERE `option_name` = '$name'" );
  955. unset( $ydb->option[ $name ] );
  956. return true;
  957. }
  958. /**
  959. * Serialize data if needed. Stolen from WordPress
  960. *
  961. * @since 1.4
  962. * @param mixed $data Data that might be serialized.
  963. * @return mixed A scalar data
  964. */
  965. function yourls_maybe_serialize( $data ) {
  966. if ( is_array( $data ) || is_object( $data ) )
  967. return serialize( $data );
  968. if ( yourls_is_serialized( $data, false ) )
  969. return serialize( $data );
  970. return $data;
  971. }
  972. /**
  973. * Check value to find if it was serialized. Stolen from WordPress
  974. *
  975. * @since 1.4
  976. * @param mixed $data Value to check to see if was serialized.
  977. * @param bool $strict Optional. Whether to be strict about the end of the string. Defaults true.
  978. * @return bool False if not serialized and true if it was.
  979. */
  980. function yourls_is_serialized( $data, $strict = true ) {
  981. // if it isn't a string, it isn't serialized
  982. if ( ! is_string( $data ) )
  983. return false;
  984. $data = trim( $data );
  985. if ( 'N;' == $data )
  986. return true;
  987. $length = strlen( $data );
  988. if ( $length < 4 )
  989. return false;
  990. if ( ':' !== $data[1] )
  991. return false;
  992. if ( $strict ) {
  993. $lastc = $data[ $length - 1 ];
  994. if ( ';' !== $lastc && '}' !== $lastc )
  995. return false;
  996. } else {
  997. $semicolon = strpos( $data, ';' );
  998. $brace = strpos( $data, '}' );
  999. // Either ; or } must exist.
  1000. if ( false === $semicolon && false === $brace )
  1001. return false;
  1002. // But neither must be in the first X characters.
  1003. if ( false !== $semicolon && $semicolon < 3 )
  1004. return false;
  1005. if ( false !== $brace && $brace < 4 )
  1006. return false;
  1007. }
  1008. $token = $data[0];
  1009. switch ( $token ) {
  1010. case 's' :
  1011. if ( $strict ) {
  1012. if ( '"' !== $data[ $length - 2 ] )
  1013. return false;
  1014. } elseif ( false === strpos( $data, '"' ) ) {
  1015. return false;
  1016. }
  1017. // or else fall through
  1018. case 'a' :
  1019. case 'O' :
  1020. return (bool) preg_match( "/^{$token}:[0-9]+:/s", $data );
  1021. case 'b' :
  1022. case 'i' :
  1023. case 'd' :
  1024. $end = $strict ? '$' : '';
  1025. return (bool) preg_match( "/^{$token}:[0-9.E-]+;$end/", $data );
  1026. }
  1027. return false;
  1028. }
  1029. /**
  1030. * Unserialize value only if it was serialized. Stolen from WP
  1031. *
  1032. * @since 1.4
  1033. * @param string $original Maybe unserialized original, if is needed.
  1034. * @return mixed Unserialized data can be any type.
  1035. */
  1036. function yourls_maybe_unserialize( $original ) {
  1037. if ( yourls_is_serialized( $original ) ) // don't attempt to unserialize data that wasn't serialized going in
  1038. return @unserialize( $original );
  1039. return $original;
  1040. }
  1041. /**
  1042. * Determine if the current page is private
  1043. *
  1044. */
  1045. function yourls_is_private() {
  1046. $private = false;
  1047. if ( defined('YOURLS_PRIVATE') && YOURLS_PRIVATE == true ) {
  1048. // Allow overruling for particular pages:
  1049. // API
  1050. if( yourls_is_API() ) {
  1051. if( !defined('YOURLS_PRIVATE_API') || YOURLS_PRIVATE_API != false )
  1052. $private = true;
  1053. // Infos
  1054. } elseif( yourls_is_infos() ) {
  1055. if( !defined('YOURLS_PRIVATE_INFOS') || YOURLS_PRIVATE_INFOS !== false )
  1056. $private = true;
  1057. // Others
  1058. } else {
  1059. $private = true;
  1060. }
  1061. }
  1062. return yourls_apply_filter( 'is_private', $private );
  1063. }
  1064. /**
  1065. * Show login form if required
  1066. *
  1067. */
  1068. function yourls_maybe_require_auth() {
  1069. if( yourls_is_private() ) {
  1070. yourls_do_action( 'require_auth' );
  1071. require_once( YOURLS_INC.'/auth.php' );
  1072. } else {
  1073. yourls_do_action( 'require_no_auth' );
  1074. }
  1075. }
  1076. /**
  1077. * Allow several short URLs for the same long URL ?
  1078. *
  1079. */
  1080. function yourls_allow_duplicate_longurls() {
  1081. // special treatment if API to check for WordPress plugin requests
  1082. if( yourls_is_API() ) {
  1083. if ( isset($_REQUEST['source']) && $_REQUEST['source'] == 'plugin' )
  1084. return false;
  1085. }
  1086. return ( defined( 'YOURLS_UNIQUE_URLS' ) && YOURLS_UNIQUE_URLS == false );
  1087. }
  1088. /**
  1089. * Return array of keywords that redirect to the submitted long URL
  1090. *
  1091. * @since 1.7
  1092. * @param string $longurl long url
  1093. * @param string $sort Optional ORDER BY order (can be 'keyword', 'title', 'timestamp' or'clicks')
  1094. * @param string $order Optional SORT order (can be 'ASC' or 'DESC')
  1095. * @return array array of keywords
  1096. */
  1097. function yourls_get_longurl_keywords( $longurl, $sort = 'none', $order = 'ASC' ) {
  1098. global $ydb;
  1099. $longurl = yourls_escape( yourls_sanitize_url( $longurl ) );
  1100. $table = YOURLS_DB_TABLE_URL;
  1101. $query = "SELECT `keyword` FROM `$table` WHERE `url` = '$longurl'";
  1102. // Ensure sort is a column in database (@TODO: update verification array if database changes)
  1103. if ( in_array( $sort, array('keyword','title','timestamp','clicks') ) ) {
  1104. $query .= " ORDER BY '".$sort."'";
  1105. if ( in_array( $order, array( 'ASC','DESC' ) ) ) {
  1106. $query .= " ".$order;
  1107. }
  1108. }
  1109. return yourls_apply_filter( 'get_longurl_keywords', $ydb->get_col( $query ), $longurl );
  1110. }
  1111. /**
  1112. * Check if an IP shortens URL too fast to prevent DB flood. Return true, or die.
  1113. *
  1114. */
  1115. function yourls_check_IP_flood( $ip = '' ) {
  1116. // Allow plugins to short-circuit the whole function
  1117. $pre = yourls_apply_filter( 'shunt_check_IP_flood', false, $ip );
  1118. if ( false !== $pre )
  1119. return $pre;
  1120. yourls_do_action( 'pre_check_ip_flood', $ip ); // at this point $ip can be '', check it if your plugin hooks in here
  1121. // Raise white flag if installing or if no flood delay defined
  1122. if(
  1123. ( defined('YOURLS_FLOOD_DELAY_SECONDS') && YOURLS_FLOOD_DELAY_SECONDS === 0 ) ||
  1124. !defined('YOURLS_FLOOD_DELAY_SECONDS') ||
  1125. yourls_is_installing()
  1126. )
  1127. return true;
  1128. // Don't throttle logged in users
  1129. if( yourls_is_private() ) {
  1130. if( yourls_is_valid_user() === true )
  1131. return true;
  1132. }
  1133. // Don't throttle whitelist IPs
  1134. if( defined( 'YOURLS_FLOOD_IP_WHITELIST' ) && YOURLS_FLOOD_IP_WHITELIST ) {
  1135. $whitelist_ips = explode( ',', YOURLS_FLOOD_IP_WHITELIST );
  1136. foreach( (array)$whitelist_ips as $whitelist_ip ) {
  1137. $whitelist_ip = trim( $whitelist_ip );
  1138. if ( $whitelist_ip == $ip )
  1139. return true;
  1140. }
  1141. }
  1142. $ip = ( $ip ? yourls_sanitize_ip( $ip ) : yourls_get_IP() );
  1143. $ip = yourls_escape( $ip );
  1144. yourls_do_action( 'check_ip_flood', $ip );
  1145. global $ydb;
  1146. $table = YOURLS_DB_TABLE_URL;
  1147. $lasttime = $ydb->get_var( "SELECT `timestamp` FROM $table WHERE `ip` = '$ip' ORDER BY `timestamp` DESC LIMIT 1" );
  1148. if( $lasttime ) {
  1149. $now = date( 'U' );
  1150. $then = date( 'U', strtotime( $lasttime ) );
  1151. if( ( $now - $then ) <= YOURLS_FLOOD_DELAY_SECONDS ) {
  1152. // Flood!
  1153. yourls_do_action( 'ip_flood', $ip, $now - $then );
  1154. yourls_die( yourls__( 'Too many URLs added too fast. Slow down please.' ), yourls__( 'Forbidden' ), 403 );
  1155. }
  1156. }
  1157. return true;
  1158. }
  1159. /**
  1160. * Check if YOURLS is installing
  1161. *
  1162. * @return bool
  1163. * @since 1.6
  1164. */
  1165. function yourls_is_installing() {
  1166. $installing = defined( 'YOURLS_INSTALLING' ) && YOURLS_INSTALLING == true;
  1167. return yourls_apply_filter( 'is_installing', $installing );
  1168. }
  1169. /**
  1170. * Check if YOURLS is upgrading
  1171. *
  1172. * @return bool
  1173. * @since 1.6
  1174. */
  1175. function yourls_is_upgrading() {
  1176. $upgrading = defined( 'YOURLS_UPGRADING' ) && YOURLS_UPGRADING == true;
  1177. return yourls_apply_filter( 'is_upgrading', $upgrading );
  1178. }
  1179. /**
  1180. * Check if YOURLS is installed
  1181. *
  1182. * Checks property $ydb->installed that is created by yourls_get_all_options()
  1183. *
  1184. * See inline comment for updating from 1.3 or prior.
  1185. *
  1186. */
  1187. function yourls_is_installed() {
  1188. global $ydb;
  1189. $is_installed = ( property_exists( $ydb, 'installed' ) && $ydb->installed == true );
  1190. return yourls_apply_filter( 'is_installed', $is_installed );
  1191. /* Note: this test won't work on YOURLS 1.3 or older (Aug 2009...)
  1192. Should someone complain that they cannot upgrade directly from
  1193. 1.3 to 1.7: first, laugh at them, then ask them to install 1.6 first.
  1194. */
  1195. }
  1196. /**
  1197. * Generate random string of (int)$length length and type $type (see function for details)
  1198. *
  1199. */
  1200. function yourls_rnd_string ( $length = 5, $type = 0, $charlist = '' ) {
  1201. $str = '';
  1202. $length = intval( $length );
  1203. // define possible characters
  1204. switch ( $type ) {
  1205. // custom char list, or comply to charset as defined in config
  1206. case '0':
  1207. $possible = $charlist ? $charlist : yourls_get_shorturl_charset() ;
  1208. break;
  1209. // no vowels to make no offending word, no 0/1/o/l to avoid confusion between letters & digits. Perfect for passwords.
  1210. case '1':
  1211. $possible = "23456789bcdfghjkmnpqrstvwxyz";
  1212. break;
  1213. // Same, with lower + upper
  1214. case '2':
  1215. $possible = "23456789bcdfghjkmnpqrstvwxyzBCDFGHJKMNPQRSTVWXYZ";
  1216. break;
  1217. // all letters, lowercase
  1218. case '3':
  1219. $possible = "abcdefghijklmnopqrstuvwxyz";
  1220. break;
  1221. // all letters, lowercase + uppercase
  1222. case '4':
  1223. $possible = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  1224. break;
  1225. // all digits & letters lowercase
  1226. case '5':
  1227. $possible = "0123456789abcdefghijklmnopqrstuvwxyz";
  1228. break;
  1229. // all digits & letters lowercase + uppercase
  1230. case '6':
  1231. $possible = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  1232. break;
  1233. }
  1234. $str = substr( str_shuffle( $possible ), 0, $length );
  1235. return yourls_apply_filter( 'rnd_string', $str, $length, $type, $charlist );
  1236. }
  1237. /**
  1238. * Return salted string
  1239. *
  1240. */
  1241. function yourls_salt( $string ) {
  1242. $salt = defined('YOURLS_COOKIEKEY') ? YOURLS_COOKIEKEY : md5(__FILE__) ;
  1243. return yourls_apply_filter( 'yourls_salt', md5 ($string . $salt), $string );
  1244. }
  1245. /**
  1246. * Add a query var to a URL and return URL. Completely stolen from WP.
  1247. *
  1248. * Works with one of these parameter patterns:
  1249. * array( 'var' => 'value' )
  1250. * array( 'var' => 'value' ), $url
  1251. * 'var', 'value'
  1252. * 'var', 'value', $url
  1253. * If $url omitted, uses $_SERVER['REQUEST_URI']
  1254. *
  1255. */
  1256. function yourls_add_query_arg() {
  1257. $ret = '';
  1258. if ( is_array( func_get_arg(0) ) ) {
  1259. if ( @func_num_args() < 2 || false === @func_get_arg( 1 ) )
  1260. $uri = $_SERVER['REQUEST_URI'];
  1261. else
  1262. $uri = @func_get_arg( 1 );
  1263. } else {
  1264. if ( @func_num_args() < 3 || false === @func_get_arg( 2 ) )
  1265. $uri = $_SERVER['REQUEST_URI'];
  1266. else
  1267. $uri = @func_get_arg( 2 );
  1268. }
  1269. $uri = str_replace( '&amp;', '&', $uri );
  1270. if ( $frag = strstr( $uri, '#' ) )
  1271. $uri = substr( $uri, 0, -strlen( $frag ) );
  1272. else
  1273. $frag = '';
  1274. if ( preg_match( '|^https?://|i', $uri, $matches ) ) {
  1275. $protocol = $matches[0];
  1276. $uri = substr( $uri, strlen( $protocol ) );
  1277. } else {
  1278. $protocol = '';
  1279. }
  1280. if ( strpos( $uri, '?' ) !== false ) {
  1281. $parts = explode( '?', $uri, 2 );
  1282. if ( 1 == count( $parts ) ) {
  1283. $base = '?';
  1284. $query = $parts[0];
  1285. } else {
  1286. $base = $parts[0] . '?';
  1287. $query = $parts[1];
  1288. }
  1289. } elseif ( !empty( $protocol ) || strpos( $uri, '=' ) === false ) {
  1290. $base = $uri . '?';
  1291. $query = '';
  1292. } else {
  1293. $base = '';
  1294. $query = $uri;
  1295. }
  1296. parse_str( $query, $qs );
  1297. $qs = yourls_urlencode_deep( $qs ); // this re-URL-encodes things that were already in the query string
  1298. if ( is_array( func_get_arg( 0 ) ) ) {
  1299. $kayvees = func_get_arg( 0 );
  1300. $qs = array_merge( $qs, $kayvees );
  1301. } else {
  1302. $qs[func_get_arg( 0 )] = func_get_arg( 1 );
  1303. }
  1304. foreach ( (array) $qs as $k => $v ) {
  1305. if ( $v === false )
  1306. unset( $qs[$k] );
  1307. }
  1308. $ret = http_build_query( $qs );
  1309. $ret = trim( $ret, '?' );
  1310. $ret = preg_replace( '#=(&|$)#', '$1', $ret );
  1311. $ret = $protocol . $base . $ret . $frag;
  1312. $ret = rtrim( $ret, '?' );
  1313. return $ret;
  1314. }
  1315. /**
  1316. * Navigates through an array and encodes the values to be used in a URL. Stolen from WP, used in yourls_add_query_arg()
  1317. *
  1318. */
  1319. function yourls_urlencode_deep( $value ) {
  1320. $value = is_array( $value ) ? array_map( 'yourls_urlencode_deep', $value ) : urlencode( $value );
  1321. return $value;
  1322. }
  1323. /**
  1324. * Remove arg from query. Opposite of yourls_add_query_arg. Stolen from WP.
  1325. *
  1326. */
  1327. function yourls_remove_query_arg( $key, $query = false ) {
  1328. if ( is_array( $key ) ) { // removing multiple keys
  1329. foreach ( $key as $k )
  1330. $query = yourls_add_query_arg( $k, false, $query );
  1331. return $query;
  1332. }
  1333. return yourls_add_query_arg( $key, false, $query );
  1334. }
  1335. /**
  1336. * Return a time-dependent string for nonce creation
  1337. *
  1338. */
  1339. function yourls_tick() {
  1340. return ceil( time() / YOURLS_NONCE_LIFE );
  1341. }
  1342. /**
  1343. * Create a time limited, action limited and user limited token
  1344. *
  1345. */
  1346. function yourls_create_nonce( $action, $user = false ) {
  1347. if( false == $user )
  1348. $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
  1349. $tick = yourls_tick();
  1350. return substr( yourls_salt($tick . $action . $user), 0, 10 );
  1351. }
  1352. /**
  1353. * Create a nonce field for inclusion into a form
  1354. *
  1355. */
  1356. function yourls_nonce_field( $action, $name = 'nonce', $user = false, $echo = true ) {
  1357. $field = '<input type="hidden" id="'.$name.'" name="'.$name.'" value="'.yourls_create_nonce( $action, $user ).'" />';
  1358. if( $echo )
  1359. echo $field."\n";
  1360. return $field;
  1361. }
  1362. /**
  1363. * Add a nonce to a URL. If URL omitted, adds nonce to current URL
  1364. *
  1365. */
  1366. function yourls_nonce_url( $action, $url = false, $name = 'nonce', $user = false ) {
  1367. $nonce = yourls_create_nonce( $action, $user );
  1368. return yourls_add_query_arg( $name, $nonce, $url );
  1369. }
  1370. /**
  1371. * Check validity of a nonce (ie time span, user and action match).
  1372. *
  1373. * Returns true if valid, dies otherwise (yourls_die() or die($return) if defined)
  1374. * if $nonce is false or unspecified, it will use $_REQUEST['nonce']
  1375. *
  1376. */
  1377. function yourls_verify_nonce( $action, $nonce = false, $user = false, $return = '' ) {
  1378. // get user
  1379. if( false == $user )
  1380. $user = defined( 'YOURLS_USER' ) ? YOURLS_USER : '-1';
  1381. // get current nonce value
  1382. if( false == $nonce && isset( $_REQUEST['nonce'] ) )
  1383. $nonce = $_REQUEST['nonce'];
  1384. // what nonce should be
  1385. $valid = yourls_create_nonce( $action, $user );
  1386. if( $nonce == $valid ) {
  1387. return true;
  1388. } else {
  1389. if( $return )
  1390. die( $return );
  1391. yourls_die( yourls__( 'Unauthorized action or expired link' ), yourls__( 'Error' ), 403 );
  1392. }
  1393. }
  1394. /**
  1395. * Converts keyword into short link (prepend with YOURLS base URL)
  1396. *
  1397. */
  1398. function yourls_link( $keyword = '' ) {
  1399. $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword );
  1400. return yourls_apply_filter( 'yourls_link', $link, $keyword );
  1401. }
  1402. /**
  1403. * Converts keyword into stat link (prepend with YOURLS base URL, append +)
  1404. *
  1405. */
  1406. function yourls_statlink( $keyword = '' ) {
  1407. $link = YOURLS_SITE . '/' . yourls_sanitize_keyword( $keyword ) . '+';
  1408. if( yourls_is_ssl() )
  1409. $link = yourls_set_url_scheme( $link, 'https' );
  1410. return yourls_apply_filter( 'yourls_statlink', $link, $keyword );
  1411. }
  1412. /**
  1413. * Check if we'll need interface display function (ie not API or redirection)
  1414. *
  1415. */
  1416. function yourls_has_interface() {
  1417. if( yourls_is_API() or yourls_is_GO() )
  1418. return false;
  1419. return true;
  1420. }
  1421. /**
  1422. * Check if we're in API mode. Returns bool
  1423. *
  1424. */
  1425. function yourls_is_API() {
  1426. if ( defined( 'YOURLS_API' ) && YOURLS_API == true )
  1427. return true;
  1428. return false;
  1429. }
  1430. /**
  1431. * Check if we're in Ajax mode. Returns bool
  1432. *
  1433. */
  1434. function yourls_is_Ajax() {
  1435. if ( defined( 'YOURLS_AJAX' ) && YOURLS_AJAX == true )
  1436. return true;
  1437. return false;
  1438. }
  1439. /**
  1440. * Check if we're in GO mode (yourls-go.php). Returns bool
  1441. *
  1442. */
  1443. function yourls_is_GO() {
  1444. if ( defined( 'YOURLS_GO' ) && YOURLS_GO == true )
  1445. return true;
  1446. return false;
  1447. }
  1448. /**
  1449. * Check if we're displaying stats infos (yourls-infos.php). Returns bool
  1450. *
  1451. */
  1452. function yourls_is_infos() {
  1453. if ( defined( 'YOURLS_INFOS' ) && YOURLS_INFOS == true )
  1454. return true;
  1455. return false;
  1456. }
  1457. /**
  1458. * Check if we're in the admin area. Returns bool
  1459. *
  1460. */
  1461. function yourls_is_admin() {
  1462. if ( defined( 'YOURLS_ADMIN' ) && YOURLS_ADMIN == true )
  1463. return true;
  1464. return false;
  1465. }
  1466. /**
  1467. * Check if the server seems to be running on Windows. Not exactly sure how reliable this is.
  1468. *
  1469. */
  1470. function yourls_is_windows() {
  1471. return defined( 'DIRECTORY_SEPARATOR' ) && DIRECTORY_SEPARATOR == '\\';
  1472. }
  1473. /**
  1474. * Check if SSL is required. Returns bool.
  1475. *
  1476. */
  1477. function yourls_needs_ssl() {
  1478. if ( defined('YOURLS_ADMIN_SSL') && YOURLS_ADMIN_SSL == true )
  1479. return true;
  1480. return false;
  1481. }
  1482. /**
  1483. * Return admin link, with SSL preference if applicable.
  1484. *
  1485. */
  1486. function yourls_admin_url( $page = '' ) {
  1487. $admin = YOURLS_SITE . '/admin/' . $page;
  1488. if( yourls_is_ssl() or yourls_needs_ssl() )
  1489. $admin = yourls_set_url_scheme( $admin, 'https' );
  1490. return yourls_apply_filter( 'admin_url', $admin, $page );
  1491. }
  1492. /**
  1493. * Return YOURLS_SITE or URL under YOURLS setup, with SSL preference
  1494. *
  1495. */
  1496. function yourls_site_url( $echo = true, $url = '' ) {
  1497. $url = yourls_get_relative_url( $url );
  1498. $url = trim( YOURLS_SITE . '/' . $url, '/' );
  1499. // Do not enforce (checking yourls_need_ssl() ) but check current usage so it won't force SSL on non-admin pages
  1500. if( yourls_is_ssl() )
  1501. $url = yourls_set_url_scheme( $url, 'https' );
  1502. $url = yourls_apply_filter( 'site_url', $url );
  1503. if( $echo )
  1504. echo $url;
  1505. return $url;
  1506. }
  1507. /**
  1508. * Check if SSL is used, returns bool. Stolen from WP.
  1509. *
  1510. */
  1511. function yourls_is_ssl() {
  1512. $is_ssl = false;
  1513. if ( isset( $_SERVER['HTTPS'] ) ) {
  1514. if ( 'on' == strtolower( $_SERVER['HTTPS'] ) )
  1515. $is_ssl = true;
  1516. if ( '1' == $_SERVER['HTTPS'] )
  1517. $is_ssl = true;
  1518. } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' == $_SERVER['SERVER_PORT'] ) ) {
  1519. $is_ssl = true;
  1520. }
  1521. return yourls_apply_filter( 'is_ssl', $is_ssl );
  1522. }
  1523. /**
  1524. * Get a remote page title
  1525. *
  1526. * This function returns a string: either the page title as defined in HTML, or the URL if not found
  1527. * The function tries to convert funky characters found in titles to UTF8, from the detected charset.
  1528. * Charset in use is guessed from HTML meta tag, or if not found, from server's 'content-type' response.
  1529. *
  1530. * @param string $url URL
  1531. * @return string Title (sanitized) or the URL if no title found
  1532. */
  1533. function yourls_get_remote_title( $url ) {
  1534. // Allow plugins to short-circuit the whole function
  1535. $pre = yourls_apply_filter( 'shunt_get_remote_title', false, $url );
  1536. if ( false !== $pre )
  1537. return $pre;
  1538. $url = yourls_sanitize_url( $url );
  1539. // Only deal with http(s)://
  1540. if( !in_array( yourls_get_protocol( $url ), array( 'http://', 'https://' ) ) )
  1541. return $url;
  1542. $title = $charset = false;
  1543. $response = yourls_http_get( $url ); // can be a Request object or an error string
  1544. if( is_string( $response ) ) {
  1545. return $url;
  1546. }
  1547. // Page content. No content? Return the URL
  1548. $content = $response->body;
  1549. if( !$content )
  1550. return $url;
  1551. // look for <title>. No title found? Return the URL
  1552. if ( preg_match('/<title>(.*?)<\/title>/is', $content, $found ) ) {
  1553. $title = $found[1];
  1554. unset( $found );
  1555. }
  1556. if( !$title )
  1557. return $url;
  1558. // Now we have a title. We'll try to get proper utf8 from it.
  1559. // Get charset as (and if) defined by the HTML meta tag. We should match
  1560. // <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  1561. // or <meta charset='utf-8'> and all possible variations: see https://gist.github.com/ozh/7951236
  1562. if ( preg_match( '/<meta[^>]*charset\s*=["\' ]*([a-zA-Z0-9\-_]+)/is', $content, $found ) ) {
  1563. $charset = $found[1];
  1564. unset( $found );
  1565. } else {
  1566. // No charset found in HTML. Get charset as (and if) defined by the server response
  1567. $_charset = current( $response->headers->getValues( 'content-type' ) );
  1568. if( preg_match( '/charset=(\S+)/', $_charset, $found ) ) {
  1569. $charset = trim( $found[1], ';' );
  1570. unset( $found );
  1571. }
  1572. }
  1573. // Conversion to utf-8 if what we have is not utf8 already
  1574. if( strtolower( $charset ) != 'utf-8' && function_exists( 'mb_convert_encoding' ) ) {
  1575. // We use @ to remove warnings because mb_ functions are easily bitching about illegal chars
  1576. if( $charset ) {
  1577. $title = @mb_convert_encoding( $title, 'UTF-8', $charset );
  1578. } else {
  1579. $title = @mb_convert_encoding( $title, 'UTF-8' );
  1580. }
  1581. }
  1582. // Remove HTML entities
  1583. $title = html_entity_decode( $title, ENT_QUOTES, 'UTF-8' );
  1584. // Strip out evil things
  1585. $title = yourls_sanitize_title( $title );
  1586. return yourls_apply_filter( 'get_remote_title', $title, $url );
  1587. }
  1588. /**
  1589. * Quick UA check for mobile devices. Return boolean.
  1590. *
  1591. */
  1592. function yourls_is_mobile_device() {
  1593. // Strings searched
  1594. $mobiles = array(
  1595. 'android', 'blackberry', 'blazer',
  1596. 'compal', 'elaine', 'fennec', 'hiptop',
  1597. 'iemobile', 'iphone', 'ipod', 'ipad',
  1598. 'iris', 'kindle', 'opera mobi', 'opera mini',
  1599. 'palm', 'phone', 'pocket', 'psp', 'symbian',
  1600. 'treo', 'wap', 'windows ce', 'windows phone'
  1601. );
  1602. // Current user-agent
  1603. $current = strtolower( $_SERVER['HTTP_USER_AGENT'] );
  1604. // Check and return
  1605. $is_mobile = ( str_replace( $mobiles, '', $current ) != $current );
  1606. return yourls_apply_filter( 'is_mobile_device', $is_mobile );
  1607. }
  1608. /**
  1609. * Get request in YOURLS base (eg in 'http://site.com/yourls/abcd' get 'abdc')
  1610. *
  1611. */
  1612. function yourls_get_request() {
  1613. // Allow plugins to short-circuit the whole function
  1614. $pre = yourls_apply_filter( 'shunt_get_request', false );
  1615. if ( false !== $pre )
  1616. return $pre;
  1617. static $request = null;
  1618. yourls_do_action( 'pre_get_request', $request );
  1619. if( $request !== null )
  1620. return $request;
  1621. // Ignore protocol & www. prefix
  1622. $root = str_replace( array( 'https://', 'http://', 'https://www.', 'http://www.' ), '', YOURLS_SITE );
  1623. // Case insensitive comparison of the YOURLS root to match both http://Sho.rt/blah and http://sho.rt/blah
  1624. $request = preg_replace( "!$root/!i", '', $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'], 1 );
  1625. // Unless request looks like a full URL (ie request is a simple keyword) strip query string
  1626. if( !preg_match( "@^[a-zA-Z]+://.+@", $request ) ) {
  1627. $request = current( explode( '?', $request ) );
  1628. }
  1629. return yourls_apply_filter( 'get_request', $request );
  1630. }
  1631. /**
  1632. * Change protocol to match current scheme used (http or https)
  1633. *
  1634. */
  1635. function yourls_match_current_protocol( $url, $normal = 'http://', $ssl = 'https://' ) {
  1636. if( yourls_is_ssl() )
  1637. $url = str_replace( $normal, $ssl, $url );
  1638. return yourls_apply_filter( 'match_current_protocol', $url );
  1639. }
  1640. /**
  1641. * Fix $_SERVER['REQUEST_URI'] variable for various setups. Stolen from WP.
  1642. *
  1643. */
  1644. function yourls_fix_request_uri() {
  1645. $default_server_values = array(
  1646. 'SERVER_SOFTWARE' => '',
  1647. 'REQUEST_URI' => '',
  1648. );
  1649. $_SERVER = array_merge( $default_server_values, $_SERVER );
  1650. // Fix for IIS when running with PHP ISAPI
  1651. if ( empty( $_SERVER['REQUEST_URI'] ) || ( php_sapi_name() != 'cgi-fcgi' && preg_match( '/^Microsoft-IIS\//', $_SERVER['SERVER_SOFTWARE'] ) ) ) {
  1652. // IIS Mod-Rewrite
  1653. if ( isset( $_SERVER['HTTP_X_ORIGINAL_URL'] ) ) {
  1654. $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_ORIGINAL_URL'];
  1655. }
  1656. // IIS Isapi_Rewrite
  1657. else if ( isset( $_SERVER['HTTP_X_REWRITE_URL'] ) ) {
  1658. $_SERVER['REQUEST_URI'] = $_SERVER['HTTP_X_REWRITE_URL'];
  1659. } else {
  1660. // Use ORIG_PATH_INFO if there is no PATH_INFO
  1661. if ( !isset( $_SERVER['PATH_INFO'] ) && isset( $_SERVER['ORIG_PATH_INFO'] ) )
  1662. $_SERVER['PATH_INFO'] = $_SERVER['ORIG_PATH_INFO'];
  1663. // Some IIS + PHP configurations puts the script-name in the path-info (No need to append it twice)
  1664. if ( isset( $_SERVER['PATH_INFO'] ) ) {
  1665. if ( $_SERVER['PATH_INFO'] == $_SERVER['SCRIPT_NAME'] )
  1666. $_SERVER['REQUEST_URI'] = $_SERVER['PATH_INFO'];
  1667. else
  1668. $_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'] . $_SERVER['PATH_INFO'];
  1669. }
  1670. // Append the query string if it exists and isn't null
  1671. if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
  1672. $_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
  1673. }
  1674. }
  1675. }
  1676. }
  1677. /**
  1678. * Shutdown function, runs just before PHP shuts down execution. Stolen from WP
  1679. *
  1680. */
  1681. function yourls_shutdown() {
  1682. yourls_do_action( 'shutdown' );
  1683. }
  1684. /**
  1685. * Auto detect custom favicon in /user directory, fallback to YOURLS favicon, and echo/return its URL
  1686. *
  1687. */
  1688. function yourls_favicon( $echo = true ) {
  1689. static $favicon = null;
  1690. if( $favicon !== null ) {
  1691. if( $echo ) {
  1692. echo $favicon;
  1693. }
  1694. return $favicon;
  1695. }
  1696. $custom = null;
  1697. // search for favicon.(gif|ico|png|jpg|svg)
  1698. foreach( array( 'gif', 'ico', 'png', 'jpg', 'svg' ) as $ext ) {
  1699. if( file_exists( YOURLS_USERDIR. '/favicon.' . $ext ) ) {
  1700. $custom = 'favicon.' . $ext;
  1701. break;
  1702. }
  1703. }
  1704. if( $custom ) {
  1705. $favicon = yourls_site_url( false, YOURLS_USERURL . '/' . $custom );
  1706. } else {
  1707. $favicon = yourls_site_url( false ) . '/images/favicon.gif';
  1708. }
  1709. if( $echo ) {
  1710. echo $favicon;
  1711. }
  1712. return $favicon;
  1713. }
  1714. /**
  1715. * Check for maintenance mode. If yes, die. See yourls_maintenance_mode(). Stolen from WP.
  1716. *
  1717. */
  1718. function yourls_check_maintenance_mode() {
  1719. $file = YOURLS_ABSPATH . '/.maintenance' ;
  1720. if ( !file_exists( $file ) || yourls_is_upgrading() || yourls_is_installing() )
  1721. return;
  1722. global $maintenance_start;
  1723. include_once( $file );
  1724. // If the $maintenance_start timestamp is older than 10 minutes, don't die.
  1725. if ( ( time() - $maintenance_start ) >= 600 )
  1726. return;
  1727. // Use any /user/maintenance.php file
  1728. if( file_exists( YOURLS_USERDIR.'/maintenance.php' ) ) {
  1729. include_once( YOURLS_USERDIR.'/maintenance.php' );
  1730. die();
  1731. }
  1732. // https://www.youtube.com/watch?v=Xw-m4jEY-Ns
  1733. $title = yourls__( 'Service temporarily unavailable' );
  1734. $message = yourls__( 'Our service is currently undergoing scheduled maintenance.' ) . "</p>\n<p>" .
  1735. yourls__( 'Things should not last very long, thank you for your patience and please excuse the inconvenience' );
  1736. yourls_die( $message, $title , 503 );
  1737. }
  1738. /**
  1739. * Return current admin page, or null if not an admin page
  1740. *
  1741. * @return mixed string if admin page, null if not an admin page
  1742. * @since 1.6
  1743. */
  1744. function yourls_current_admin_page() {
  1745. if( yourls_is_admin() ) {
  1746. $current = substr( yourls_get_request(), 6 );
  1747. if( $current === false )
  1748. $current = 'index.php'; // if current page is http://sho.rt/admin/ instead of http://sho.rt/admin/index.php
  1749. return $current;
  1750. }
  1751. return null;
  1752. }
  1753. /**
  1754. * Check if a URL protocol is allowed
  1755. *
  1756. * Checks a URL against a list of whitelisted protocols. Protocols must be defined with
  1757. * their complete scheme name, ie 'stuff:' or 'stuff://' (for instance, 'mailto:' is a valid
  1758. * protocol, 'mailto://' isn't, and 'http:' with no double slashed isn't either
  1759. *
  1760. * @since 1.6
  1761. *
  1762. * @param string $url URL to be check
  1763. * @param array $protocols Optional. Array of protocols, defaults to global $yourls_allowedprotocols
  1764. * @return boolean true if protocol allowed, false otherwise
  1765. */
  1766. function yourls_is_allowed_protocol( $url, $protocols = array() ) {
  1767. if( ! $protocols ) {
  1768. global $yourls_allowedprotocols;
  1769. $protocols = $yourls_allowedprotocols;
  1770. }
  1771. $protocol = yourls_get_protocol( $url );
  1772. return yourls_apply_filter( 'is_allowed_protocol', in_array( $protocol, $protocols ), $url, $protocols );
  1773. }
  1774. /**
  1775. * Get protocol from a URL (eg mailto:, http:// ...)
  1776. *
  1777. * @since 1.6
  1778. *
  1779. * @param string $url URL to be check
  1780. * @return string Protocol, with slash slash if applicable. Empty string if no protocol
  1781. */
  1782. function yourls_get_protocol( $url ) {
  1783. preg_match( '!^[a-zA-Z0-9\+\.-]+:(//)?!', $url, $matches );
  1784. /*
  1785. http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax
  1786. The scheme name consists of a sequence of characters beginning with a letter and followed by any
  1787. combination of letters, digits, plus ("+"), period ("."), or hyphen ("-"). Although schemes are
  1788. case-insensitive, the canonical form is lowercase and documents that specify schemes must do so
  1789. with lowercase letters. It is followed by a colon (":").
  1790. */
  1791. $protocol = ( isset( $matches[0] ) ? $matches[0] : '' );
  1792. return yourls_apply_filter( 'get_protocol', $protocol, $url );
  1793. }
  1794. /**
  1795. * Get relative URL (eg 'abc' from 'http://sho.rt/abc')
  1796. *
  1797. * Treat indifferently http & https. If a URL isn't relative to the YOURLS install, return it as is
  1798. * or return empty string if $strict is true
  1799. *
  1800. * @since 1.6
  1801. * @param string $url URL to relativize
  1802. * @param bool $strict if true and if URL isn't relative to YOURLS install, return empty string
  1803. * @return string URL
  1804. */
  1805. function yourls_get_relative_url( $url, $strict = true ) {
  1806. $url = yourls_sanitize_url( $url );
  1807. // Remove protocols to make it easier
  1808. $noproto_url = str_replace( 'https:', 'http:', $url );
  1809. $noproto_site = str_replace( 'https:', 'http:', YOURLS_SITE );
  1810. // Trim URL from YOURLS root URL : if no modification made, URL wasn't relative
  1811. $_url = str_replace( $noproto_site . '/', '', $noproto_url );
  1812. if( $_url == $noproto_url )
  1813. $_url = ( $strict ? '' : $url );
  1814. return yourls_apply_filter( 'get_relative_url', $_url, $url );
  1815. }
  1816. /**
  1817. * Marks a function as deprecated and informs when it has been used. Stolen from WP.
  1818. *
  1819. * There is a hook deprecated_function that will be called that can be used
  1820. * to get the backtrace up to what file and function called the deprecated
  1821. * function.
  1822. *
  1823. * The current behavior is to trigger a user error if YOURLS_DEBUG is true.
  1824. *
  1825. * This function is to be used in every function that is deprecated.
  1826. *
  1827. * @since 1.6
  1828. * @uses yourls_do_action() Calls 'deprecated_function' and passes the function name, what to use instead,
  1829. * and the version the function was deprecated in.
  1830. * @uses yourls_apply_filters() Calls 'deprecated_function_trigger_error' and expects boolean value of true to do
  1831. * trigger or false to not trigger error.
  1832. *
  1833. * @param string $function The function that was called
  1834. * @param string $version The version of WordPress that deprecated the function
  1835. * @param string $replacement Optional. The function that should have been called
  1836. */
  1837. function yourls_deprecated_function( $function, $version, $replacement = null ) {
  1838. yourls_do_action( 'deprecated_function', $function, $replacement, $version );
  1839. // Allow plugin to filter the output error trigger
  1840. if ( YOURLS_DEBUG && yourls_apply_filters( 'deprecated_function_trigger_error', true ) ) {
  1841. if ( ! is_null( $replacement ) )
  1842. trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s! Use %3$s instead.'), $function, $version, $replacement ) );
  1843. else
  1844. trigger_error( sprintf( yourls__('%1$s is <strong>deprecated</strong> since version %2$s with no alternative available.'), $function, $version ) );
  1845. }
  1846. }
  1847. /**
  1848. * Return the value if not an empty string
  1849. *
  1850. * Used with array_filter(), to remove empty keys but not keys with value 0 or false
  1851. *
  1852. * @since 1.6
  1853. * @param mixed $val Value to test against ''
  1854. * @return bool True if not an empty string
  1855. */
  1856. function yourls_return_if_not_empty_string( $val ) {
  1857. return( $val !== '' );
  1858. }
  1859. /**
  1860. * Add a message to the debug log
  1861. *
  1862. * When in debug mode ( YOURLS_DEBUG == true ) the debug log is echoed in yourls_html_footer()
  1863. * Log messages are appended to $ydb->debug_log array, which is instanciated within class ezSQLcore_YOURLS
  1864. *
  1865. * @since 1.7
  1866. * @param string $msg Message to add to the debug log
  1867. * @return string The message itself
  1868. */
  1869. function yourls_debug_log( $msg ) {
  1870. global $ydb;
  1871. $ydb->debug_log[] = $msg;
  1872. return $msg;
  1873. }
  1874. /**
  1875. * Explode a URL in an array of ( 'protocol' , 'slashes if any', 'rest of the URL' )
  1876. *
  1877. * Some hosts trip up when a query string contains 'http://' - see http://git.io/j1FlJg
  1878. * The idea is that instead of passing the whole URL to a bookmarklet, eg index.php?u=http://blah.com,
  1879. * we pass it by pieces to fool the server, eg index.php?proto=http:&slashes=//&rest=blah.com
  1880. *
  1881. * Known limitation: this won't work if the rest of the URL itself contains 'http://', for example
  1882. * if rest = blah.com/file.php?url=http://foo.com
  1883. *
  1884. * Sample returns:
  1885. *
  1886. * with 'mailto:jsmith@example.com?subject=hey' :
  1887. * array( 'protocol' => 'mailto:', 'slashes' => '', 'rest' => 'jsmith@example.com?subject=hey' )
  1888. *
  1889. * with 'http://example.com/blah.html' :
  1890. * array( 'protocol' => 'http:', 'slashes' => '//', 'rest' => 'example.com/blah.html' )
  1891. *
  1892. * @since 1.7
  1893. * @param string $url URL to be parsed
  1894. * @param array $array Optional, array of key names to be used in returned array
  1895. * @return mixed false if no protocol found, array of ('protocol' , 'slashes', 'rest') otherwise
  1896. */
  1897. function yourls_get_protocol_slashes_and_rest( $url, $array = array( 'protocol', 'slashes', 'rest' ) ) {
  1898. $proto = yourls_get_protocol( $url );
  1899. if( !$proto or count( $array ) != 3 )
  1900. return false;
  1901. list( $null, $rest ) = explode( $proto, $url, 2 );
  1902. list( $proto, $slashes ) = explode( ':', $proto );
  1903. return array( $array[0] => $proto . ':', $array[1] => $slashes, $array[2] => $rest );
  1904. }
  1905. /**
  1906. * Set URL scheme (to HTTP or HTTPS)
  1907. *
  1908. * @since 1.7.1
  1909. * @param string $url URL
  1910. * @param string $scheme scheme, either 'http' or 'https'
  1911. * @return string URL with chosen scheme
  1912. */
  1913. function yourls_set_url_scheme( $url, $scheme = false ) {
  1914. if( $scheme != 'http' && $scheme != 'https' ) {
  1915. return $url;
  1916. }
  1917. return preg_replace( '!^[a-zA-Z0-9\+\.-]+://!', $scheme . '://', $url );
  1918. }