KXStudio Website https://kx.studio/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

helpers.php 56KB

9 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632
  1. <?php
  2. /**
  3. * File: Helpers
  4. * Various functions used throughout Chyrp's code.
  5. */
  6. # Integer: $time_start
  7. # Times Chyrp.
  8. $time_start = 0;
  9. # Array: $l10n
  10. # Stores loaded gettext domains.
  11. $l10n = array();
  12. /**
  13. * Function: session
  14. * Begins Chyrp's custom session storage whatnots.
  15. */
  16. function session() {
  17. session_set_save_handler(array("Session", "open"),
  18. array("Session", "close"),
  19. array("Session", "read"),
  20. array("Session", "write"),
  21. array("Session", "destroy"),
  22. array("Session", "gc"));
  23. $domain = (substr_count($_SERVER['HTTP_HOST'], ".")) ? preg_replace("/^www\./", ".", $_SERVER['HTTP_HOST']) : "" ;
  24. session_set_cookie_params(60 * 60 * 24 * 30, "/", $domain);
  25. session_name("ChyrpSession");
  26. register_shutdown_function("session_write_close");
  27. session_start();
  28. }
  29. /**
  30. * Function: error
  31. * Shows an error message.
  32. *
  33. * Parameters:
  34. * $title - The title for the error dialog.
  35. * $body - The message for the error dialog.
  36. * $backtrace - The trace of the error.
  37. */
  38. function error($title, $body, $backtrace = array()) {
  39. if (defined('MAIN_DIR') and !empty($backtrace))
  40. foreach ($backtrace as $index => &$trace)
  41. if (!isset($trace["file"]) or !isset($trace["line"]))
  42. unset($backtrace[$index]);
  43. else
  44. $trace["file"] = str_replace(MAIN_DIR."/", "", $trace["file"]);
  45. # $trace["file"] = isset($trace["file"]) ?
  46. # :
  47. # (isset($trace["function"]) ?
  48. # (isset($trace["class"]) ?
  49. # $trace["class"].$trace["type"] :
  50. # "").$trace["function"] :
  51. # "[internal]");
  52. # Clear all output sent before this error.
  53. if (($buffer = ob_get_contents()) !== false) {
  54. ob_end_clean();
  55. # Since the header might already be set to gzip, start output buffering again.
  56. if (extension_loaded("zlib") and !ini_get("zlib.output_compression") and
  57. isset($_SERVER['HTTP_ACCEPT_ENCODING']) and
  58. substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], "gzip") and
  59. USE_ZLIB) {
  60. ob_start("ob_gzhandler");
  61. header("Content-Encoding: gzip");
  62. } else
  63. ob_start();
  64. } elseif (!UPGRADING) {
  65. # If output buffering is not started, assume this
  66. # is sent from the Session class or somewhere deep.
  67. error_log($title.": ".$body);
  68. foreach ($backtrace as $index => $trace)
  69. error_log(" ".($index + 1).": "._f("%s on line %d", array($trace["file"], $trace["line"])));
  70. exit;
  71. }
  72. if (TESTER)
  73. exit("ERROR: ".$body);
  74. if ($title == __("Access Denied"))
  75. $_SESSION['redirect_to'] = self_url();
  76. # Display the error.
  77. if (defined('THEME_DIR') and class_exists("Theme") and Theme::current()->file_exists("pages/error"))
  78. MainController::current()->display("pages/error",
  79. array("title" => $title,
  80. "body" => $body,
  81. "backtrace" => $backtrace));
  82. else
  83. require INCLUDES_DIR."/error.php";
  84. if ($buffer !== false)
  85. ob_end_flush();
  86. exit;
  87. }
  88. /**
  89. * Function: show_403
  90. * Shows an error message with a 403 HTTP header.
  91. *
  92. * Parameters:
  93. * $title - The title for the error dialog.
  94. * $body - The message for the error dialog.
  95. */
  96. function show_403($title, $body) {
  97. header("Status: 403");
  98. error($title, $body);
  99. }
  100. /**
  101. * Function: logged_in
  102. * Returns whether or not they are logged in by returning the <Visitor.$id> (which defaults to 0).
  103. */
  104. function logged_in() {
  105. return (class_exists("Visitor") and isset(Visitor::current()->id) and Visitor::current()->id != 0);
  106. }
  107. /**
  108. * Function: load_translator
  109. * Loads a .mo file for gettext translation.
  110. *
  111. * Parameters:
  112. * $domain - The name for this translation domain.
  113. * $mofile - The .mo file to read from.
  114. */
  115. function load_translator($domain, $mofile) {
  116. global $l10n;
  117. if (isset($l10n[$domain]))
  118. return;
  119. if (is_readable($mofile))
  120. $input = new CachedFileReader($mofile);
  121. else
  122. return;
  123. $l10n[$domain] = new gettext_reader($input);
  124. }
  125. /**
  126. * Function: __
  127. * Returns a translated string.
  128. *
  129. * Parameters:
  130. * $text - The string to translate.
  131. * $domain - The translation domain to read from.
  132. */
  133. function __($text, $domain = "chyrp") {
  134. global $l10n;
  135. return (isset($l10n[$domain])) ? $l10n[$domain]->translate($text) : $text ;
  136. }
  137. /**
  138. * Function: _p
  139. * Returns a plural (or not) form of a translated string.
  140. *
  141. * Parameters:
  142. * $single - Singular string.
  143. * $plural - Pluralized string.
  144. * $number - The number to judge by.
  145. * $domain - The translation domain to read from.
  146. */
  147. function _p($single, $plural, $number, $domain = "chyrp") {
  148. global $l10n;
  149. return isset($l10n[$domain]) ?
  150. $l10n[$domain]->ngettext($single, $plural, $number) :
  151. (($number != 1) ? $plural : $single) ;
  152. }
  153. /**
  154. * Function: _f
  155. * Returns a formatted translated string.
  156. *
  157. * Parameters:
  158. * $string - String to translate and format.
  159. * $args - One arg or an array of arguments to format with.
  160. * $domain - The translation domain to read from.
  161. */
  162. function _f($string, $args = array(), $domain = "chyrp") {
  163. $args = (array) $args;
  164. array_unshift($args, __($string, $domain));
  165. return call_user_func_array("sprintf", $args);
  166. }
  167. /**
  168. * Function: redirect
  169. * Redirects to the given URL and exits immediately.
  170. *
  171. * Parameters:
  172. * $url - The URL to redirect to. If it begins with @/@ it will be relative to the @Config.chyrp_url@.
  173. * $use_chyrp_url - Use the @Config.chyrp_url@ instead of @Config.url@ for $urls beginning with @/@?
  174. */
  175. function redirect($url, $use_chyrp_url = false) {
  176. # Handle URIs without domain
  177. if ($url[0] == "/")
  178. $url = (ADMIN or $use_chyrp_url) ?
  179. Config::current()->chyrp_url.$url :
  180. Config::current()->url.$url ;
  181. elseif (file_exists(INCLUDES_DIR."/config.yaml.php") and class_exists("Route") and !substr_count($url, "://"))
  182. $url = url($url);
  183. header("Location: ".html_entity_decode($url));
  184. exit;
  185. }
  186. /**
  187. * Function: url
  188. * Mask for Route->url().
  189. */
  190. function url($url, $controller = null) {
  191. return Route::current()->url($url, $controller);
  192. }
  193. /**
  194. * Function: pluralize
  195. * Returns a pluralized string. This is a port of Rails's pluralizer.
  196. *
  197. * Parameters:
  198. * $string - The string to pluralize.
  199. * $number - If passed, and this number is 1, it will not pluralize.
  200. */
  201. function pluralize($string, $number = null) {
  202. $uncountable = array("moose", "sheep", "fish", "series", "species",
  203. "rice", "money", "information", "equipment", "piss");
  204. if (in_array($string, $uncountable) or $number == 1)
  205. return $string;
  206. $replacements = array("/person/i" => "people",
  207. "/man/i" => "men",
  208. "/child/i" => "children",
  209. "/cow/i" => "kine",
  210. "/goose/i" => "geese",
  211. "/(penis)$/i" => "\\1es", # Take that, Rails!
  212. "/(ax|test)is$/i" => "\\1es",
  213. "/(octop|vir)us$/i" => "\\1ii",
  214. "/(cact)us$/i" => "\\1i",
  215. "/(alias|status)$/i" => "\\1es",
  216. "/(bu)s$/i" => "\\1ses",
  217. "/(buffal|tomat)o$/i" => "\\1oes",
  218. "/([ti])um$/i" => "\\1a",
  219. "/sis$/i" => "ses",
  220. "/(hive)$/i" => "\\1s",
  221. "/([^aeiouy]|qu)y$/i" => "\\1ies",
  222. "/^(ox)$/i" => "\\1en",
  223. "/(matr|vert|ind)(?:ix|ex)$/i" => "\\1ices",
  224. "/(x|ch|ss|sh)$/i" => "\\1es",
  225. "/([m|l])ouse$/i" => "\\1ice",
  226. "/(quiz)$/i" => "\\1zes");
  227. $replaced = preg_replace(array_keys($replacements), array_values($replacements), $string, 1);
  228. if ($replaced == $string)
  229. return $string."s";
  230. else
  231. return $replaced;
  232. }
  233. /**
  234. * Function: depluralize
  235. * Returns a depluralized string. This is the inverse of <pluralize>.
  236. *
  237. * Parameters:
  238. * $string - The string to depluralize.
  239. * $number - If passed, and this number is not 1, it will not depluralize.
  240. */
  241. function depluralize($string, $number = null) {
  242. if (isset($number) and $number != 1)
  243. return $string;
  244. $replacements = array("/people/i" => "person",
  245. "/^men/i" => "man",
  246. "/children/i" => "child",
  247. "/kine/i" => "cow",
  248. "/geese/i" => "goose",
  249. "/(penis)es$/i" => "\\1",
  250. "/(ax|test)es$/i" => "\\1is",
  251. "/(octopi|viri|cact)i$/i" => "\\1us",
  252. "/(alias|status)es$/i" => "\\1",
  253. "/(bu)ses$/i" => "\\1s",
  254. "/(buffal|tomat)oes$/i" => "\\1o",
  255. "/([ti])a$/i" => "\\1um",
  256. "/ses$/i" => "sis",
  257. "/(hive)s$/i" => "\\1",
  258. "/([^aeiouy]|qu)ies$/i" => "\\1y",
  259. "/^(ox)en$/i" => "\\1",
  260. "/(vert|ind)ices$/i" => "\\1ex",
  261. "/(matr)ices$/i" => "\\1ix",
  262. "/(x|ch|ss|sh)es$/i" => "\\1",
  263. "/([ml])ice$/i" => "\\1ouse",
  264. "/(quiz)zes$/i" => "\\1");
  265. $replaced = preg_replace(array_keys($replacements), array_values($replacements), $string, 1);
  266. if ($replaced == $string and substr($string, -1) == "s")
  267. return substr($string, 0, -1);
  268. else
  269. return $replaced;
  270. }
  271. /**
  272. * Function: truncate
  273. * Truncates a string to the given length, optionally taking into account HTML tags, and/or keeping words in tact.
  274. *
  275. * Parameters:
  276. * $text - String to shorten.
  277. * $length - Length to truncate to.
  278. * $ending - What to place at the end, e.g. "...".
  279. * $exact - Break words?
  280. * $html - Auto-close cut-off HTML tags?
  281. *
  282. * Author:
  283. * CakePHP team, code style modified.
  284. */
  285. function truncate($text, $length = 100, $ending = "...", $exact = false, $html = false) {
  286. if (is_array($ending))
  287. extract($ending);
  288. if ($html) {
  289. if (strlen(preg_replace("/<[^>]+>/", "", $text)) <= $length)
  290. return $text;
  291. $totalLength = strlen($ending);
  292. $openTags = array();
  293. $truncate = "";
  294. preg_match_all("/(<\/?([\w+]+)[^>]*>)?([^<>]*)/", $text, $tags, PREG_SET_ORDER);
  295. foreach ($tags as $tag) {
  296. if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])
  297. and preg_match('/<[\w]+[^>]*>/s', $tag[0]))
  298. array_unshift($openTags, $tag[2]);
  299. elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) {
  300. $pos = array_search($closeTag[1], $openTags);
  301. if ($pos !== false)
  302. array_splice($openTags, $pos, 1);
  303. }
  304. $truncate .= $tag[1];
  305. $contentLength = strlen(preg_replace("/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i", " ", $tag[3]));
  306. if ($contentLength + $totalLength > $length) {
  307. $left = $length - $totalLength;
  308. $entitiesLength = 0;
  309. if (preg_match_all("/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i", $tag[3], $entities, PREG_OFFSET_CAPTURE))
  310. foreach ($entities[0] as $entity)
  311. if ($entity[1] + 1 - $entitiesLength <= $left) {
  312. $left--;
  313. $entitiesLength += strlen($entity[0]);
  314. } else
  315. break;
  316. $truncate .= substr($tag[3], 0 , $left + $entitiesLength);
  317. break;
  318. } else {
  319. $truncate .= $tag[3];
  320. $totalLength += $contentLength;
  321. }
  322. if ($totalLength >= $length)
  323. break;
  324. }
  325. } else {
  326. if (strlen($text) <= $length)
  327. return $text;
  328. else
  329. $truncate = substr($text, 0, $length - strlen($ending));
  330. }
  331. if (!$exact) {
  332. $spacepos = strrpos($truncate, " ");
  333. if (isset($spacepos)) {
  334. if ($html) {
  335. $bits = substr($truncate, $spacepos);
  336. preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER);
  337. if (!empty($droppedTags))
  338. foreach ($droppedTags as $closingTag)
  339. if (!in_array($closingTag[1], $openTags))
  340. array_unshift($openTags, $closingTag[1]);
  341. }
  342. $truncate = substr($truncate, 0, $spacepos);
  343. }
  344. }
  345. $truncate .= $ending;
  346. if ($html)
  347. foreach ($openTags as $tag)
  348. $truncate .= '</'.$tag.'>';
  349. return $truncate;
  350. }
  351. /**
  352. * Function: when
  353. * Returns date formatting for a string that isn't a regular time() value
  354. *
  355. * Parameters:
  356. * $formatting - The formatting for date().
  357. * $when - Time to base on. If it is not numeric it will be run through strtotime.
  358. * $strftime - Use @strftime@ instead of @date@?
  359. */
  360. function when($formatting, $when, $strftime = false) {
  361. $time = (is_numeric($when)) ? $when : strtotime($when) ;
  362. if ($strftime)
  363. return strftime($formatting, $time);
  364. else
  365. return date($formatting, $time);
  366. }
  367. /**
  368. * Function: datetime
  369. * Returns a standard datetime string based on either the passed timestamp or their time offset, usually for MySQL inserts.
  370. *
  371. * Parameters:
  372. * $when - An optional timestamp.
  373. */
  374. function datetime($when = null) {
  375. fallback($when, time());
  376. $time = (is_numeric($when)) ? $when : strtotime($when) ;
  377. return date("Y-m-d H:i:s", $time);
  378. }
  379. /**
  380. * Function: fix
  381. * Returns a HTML-sanitized version of a string.
  382. *
  383. * Parameters:
  384. * $string - String to fix.
  385. * $quotes - Encode quotes?
  386. */
  387. function fix($string, $quotes = false) {
  388. $quotes = ($quotes) ? ENT_QUOTES : ENT_NOQUOTES ;
  389. return htmlspecialchars($string, $quotes, "utf-8");
  390. }
  391. /**
  392. * Function: unfix
  393. * Returns the reverse of fix().
  394. *
  395. * Parameters:
  396. * $string - String to unfix.
  397. */
  398. function unfix($string) {
  399. return htmlspecialchars_decode($string, ENT_QUOTES, "utf-8");
  400. }
  401. /**
  402. * Function: lang_code
  403. * Returns the passed language code (e.g. en_US) to the human-readable text (e.g. English (US))
  404. *
  405. * Parameters:
  406. * $code - The language code to convert
  407. *
  408. * Author:
  409. * TextPattern devs, modified to fit with Chyrp.
  410. */
  411. function lang_code($code) {
  412. $langs = array("ar_DZ" => "جزائري عربي",
  413. "ca_ES" => "Català",
  414. "cs_CZ" => "Čeština",
  415. "da_DK" => "Dansk",
  416. "de_DE" => "Deutsch",
  417. "el_GR" => "Ελληνικά",
  418. "en_GB" => "English (GB)",
  419. "en_US" => "English (US)",
  420. "es_ES" => "Español",
  421. "et_EE" => "Eesti",
  422. "fi_FI" => "Suomi",
  423. "fr_FR" => "Français",
  424. "gl_GZ" => "Galego (Galiza)",
  425. "he_IL" => "עברית",
  426. "hu_HU" => "Magyar",
  427. "id_ID" => "Bahasa Indonesia",
  428. "is_IS" => "Íslenska",
  429. "it_IT" => "Italiano",
  430. "ja_JP" => "日本語",
  431. "lv_LV" => "Latviešu",
  432. "nl_NL" => "Nederlands",
  433. "no_NO" => "Norsk",
  434. "pl_PL" => "Polski",
  435. "pt_PT" => "Português",
  436. "ro_RO" => "Română",
  437. "ru_RU" => "Русский",
  438. "sk_SK" => "Slovenčina",
  439. "sv_SE" => "Svenska",
  440. "th_TH" => "ไทย",
  441. "uk_UA" => "Українська",
  442. "vi_VN" => "Tiếng Việt",
  443. "zh_CN" => "中文(简体)",
  444. "zh_TW" => "中文(繁體)",
  445. "bg_BG" => "Български");
  446. return (isset($langs[$code])) ? str_replace(array_keys($langs), array_values($langs), $code) : $code ;
  447. }
  448. /**
  449. * Function: sanitize
  450. * Returns a sanitized string, typically for URLs.
  451. *
  452. * Parameters:
  453. * $string - The string to sanitize.
  454. * $force_lowercase - Force the string to lowercase?
  455. * $anal - If set to *true*, will remove all non-alphanumeric characters.
  456. * $trunc - Number of characters to truncate to (default 100, 0 to disable).
  457. */
  458. function sanitize($string, $force_lowercase = true, $anal = false, $trunc = 100) {
  459. $strip = array("~", "`", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "=", "+", "[", "{", "]",
  460. "}", "\\", "|", ";", ":", "\"", "'", "&#8216;", "&#8217;", "&#8220;", "&#8221;", "&#8211;", "&#8212;",
  461. "—", "–", ",", "<", ".", ">", "/", "?");
  462. $clean = trim(str_replace($strip, "", strip_tags($string)));
  463. $clean = preg_replace('/\s+/', "-", $clean);
  464. $clean = ($anal ? preg_replace("/[^a-zA-Z0-9]/", "", $clean) : $clean);
  465. $clean = ($trunc ? substr($clean, 0, $trunc) : $clean);
  466. return ($force_lowercase) ?
  467. (function_exists('mb_strtolower')) ?
  468. mb_strtolower($clean, 'UTF-8') :
  469. strtolower($clean) :
  470. $clean;
  471. }
  472. /**
  473. * Function: trackback_respond
  474. * Responds to a trackback request.
  475. *
  476. * Parameters:
  477. * $error - Is this an error?
  478. * $message - Message to return.
  479. */
  480. function trackback_respond($error = false, $message = "") {
  481. header("Content-Type: text/xml; charset=utf-8");
  482. if ($error) {
  483. echo '<?xml version="1.0" encoding="utf-8"?'.">\n";
  484. echo "<response>\n";
  485. echo "<error>1</error>\n";
  486. echo "<message>".$message."</message>\n";
  487. echo "</response>";
  488. exit;
  489. } else {
  490. echo '<?xml version="1.0" encoding="utf-8"?'.">\n";
  491. echo "<response>\n";
  492. echo "<error>0</error>\n";
  493. echo "</response>";
  494. }
  495. exit;
  496. }
  497. /**
  498. * Function: trackback_send
  499. * Sends a trackback request.
  500. *
  501. * Parameters:
  502. * $post - The post we're sending from.
  503. * $target - The URL we're sending to.
  504. */
  505. function trackback_send($post, $target) {
  506. if (empty($target)) return false;
  507. $target = parse_url($target);
  508. $title = $post->title();
  509. fallback($title, ucfirst($post->feather)." Post #".$post->id);
  510. $excerpt = strip_tags(truncate($post->excerpt(), 255));
  511. if (!empty($target["query"])) $target["query"] = "?".$target["query"];
  512. if (empty($target["port"])) $target["port"] = 80;
  513. $connect = fsockopen($target["host"], $target["port"]);
  514. if (!$connect) return false;
  515. $config = Config::current();
  516. $query = "url=".rawurlencode($post->url())."&".
  517. "title=".rawurlencode($title)."&".
  518. "blog_name=".rawurlencode($config->name)."&".
  519. "excerpt=".rawurlencode($excerpt);
  520. fwrite($connect, "POST ".$target["path"].$target["query"]." HTTP/1.1\n");
  521. fwrite($connect, "Host: ".$target["host"]."\n");
  522. fwrite($connect, "Content-type: application/x-www-form-urlencoded\n");
  523. fwrite($connect, "Content-length: ". strlen($query)."\n");
  524. fwrite($connect, "Connection: close\n\n");
  525. fwrite($connect, $query);
  526. fclose($connect);
  527. return true;
  528. }
  529. /**
  530. * Function: send_pingbacks
  531. * Sends pingback requests to the URLs in a string.
  532. *
  533. * Parameters:
  534. * $string - The string to crawl for pingback URLs.
  535. * $post - The post we're sending from.
  536. */
  537. function send_pingbacks($string, $post) {
  538. foreach (grab_urls($string) as $url)
  539. if ($ping_url = pingback_url($url)) {
  540. require_once INCLUDES_DIR."/lib/ixr.php";
  541. $client = new IXR_Client($ping_url);
  542. $client->timeout = 3;
  543. $client->useragent.= " -- Chyrp/".CHYRP_VERSION;
  544. $client->query("pingback.ping", $post->url(), $url);
  545. }
  546. }
  547. /**
  548. * Function: grab_urls
  549. * Crawls a string for links.
  550. *
  551. * Parameters:
  552. * $string - The string to crawl.
  553. *
  554. * Returns:
  555. * An array of all URLs found in the string.
  556. */
  557. function grab_urls($string) {
  558. $regexp = "/<a[^>]+href=[\"|']([^\"]+)[\"|']>[^<]+<\/a>/";
  559. preg_match_all(Trigger::current()->filter($regexp, "link_regexp"), stripslashes($string), $matches);
  560. $matches = $matches[1];
  561. return $matches;
  562. }
  563. /**
  564. * Function: pingback_url
  565. * Checks if a URL is pingback-capable.
  566. *
  567. * Parameters:
  568. * $url - The URL to check.
  569. *
  570. * Returns:
  571. * The pingback target, if the URL is pingback-capable.
  572. */
  573. function pingback_url($url) {
  574. extract(parse_url($url), EXTR_SKIP);
  575. if (!isset($host)) return false;
  576. $path = (!isset($path)) ? '/' : $path ;
  577. if (isset($query)) $path.= '?'.$query;
  578. $port = (isset($port)) ? $port : 80 ;
  579. # Connect
  580. $connect = @fsockopen($host, $port, $errno, $errstr, 2);
  581. if (!$connect) return false;
  582. # Send the GET headers
  583. fwrite($connect, "GET $path HTTP/1.1\r\n");
  584. fwrite($connect, "Host: $host\r\n");
  585. fwrite($connect, "User-Agent: Chyrp/".CHYRP_VERSION."\r\n\r\n");
  586. # Check for X-Pingback header
  587. $headers = "";
  588. while (!feof($connect)) {
  589. $line = fgets($connect, 512);
  590. if (trim($line) == "") break;
  591. $headers.= trim($line)."\n";
  592. if (preg_match("/X-Pingback: (.+)/i", $line, $matches))
  593. return trim($matches[1]);
  594. # Nothing's found so far, so grab the content-type
  595. # for the <link> search afterwards
  596. if (preg_match("/Content-Type: (.+)/i", $headers, $matches))
  597. $content_type = trim($matches[1]);
  598. }
  599. # No header found, check for <link>
  600. if (preg_match('/(image|audio|video|model)/i', $content_type)) return false;
  601. $size = 0;
  602. while (!feof($connect)) {
  603. $line = fgets($connect, 1024);
  604. if (preg_match("/<link rel=[\"|']pingback[\"|'] href=[\"|']([^\"]+)[\"|'] ?\/?>/i", $line, $link))
  605. return $link[1];
  606. $size += strlen($line);
  607. if ($size > 2048) return false;
  608. }
  609. fclose($connect);
  610. return false;
  611. }
  612. /**
  613. * Function: camelize
  614. * Converts a given string to camel-case.
  615. *
  616. * Parameters:
  617. * $string - The string to camelize.
  618. * $keep_spaces - Whether or not to convert underscores to spaces or remove them.
  619. *
  620. * Returns:
  621. * A CamelCased string.
  622. *
  623. * See Also:
  624. * <decamelize>
  625. */
  626. function camelize($string, $keep_spaces = false) {
  627. $lower = strtolower($string);
  628. $deunderscore = str_replace("_", " ", $lower);
  629. $dehyphen = str_replace("-", " ", $deunderscore);
  630. $final = ucwords($dehyphen);
  631. if (!$keep_spaces)
  632. $final = str_replace(" ", "", $final);
  633. return $final;
  634. }
  635. /**
  636. * Function: decamelize
  637. * Decamelizes a string.
  638. *
  639. * Parameters:
  640. * $string - The string to decamelize.
  641. *
  642. * Returns:
  643. * A de_camel_cased string.
  644. *
  645. * See Also:
  646. * <camelize>
  647. */
  648. function decamelize($string) {
  649. return strtolower(preg_replace("/([a-z])([A-Z])/", "\\1_\\2", $string));
  650. }
  651. /**
  652. * Function: selected
  653. * If $val1 == $val2, outputs or returns @ selected="selected"@
  654. *
  655. * Parameters:
  656. * $val1 - First value.
  657. * $val2 - Second value.
  658. * $return - Return @ selected="selected"@ instead of outputting it
  659. */
  660. function selected($val1, $val2, $return = false) {
  661. if ($val1 == $val2)
  662. if ($return)
  663. return ' selected="selected"';
  664. else
  665. echo ' selected="selected"';
  666. }
  667. /**
  668. * Function: checked
  669. * If $val == 1 (true), outputs ' checked="checked"'
  670. *
  671. * Parameters:
  672. * $val - Value to check.
  673. */
  674. function checked($val) {
  675. if ($val == 1) echo ' checked="checked"';
  676. }
  677. /**
  678. * Function: module_enabled
  679. * Returns whether the given module is enabled or not.
  680. *
  681. * Parameters:
  682. * $name - The folder name of the module.
  683. *
  684. * Returns:
  685. * Whether or not the requested module is enabled.
  686. */
  687. function module_enabled($name) {
  688. $config = Config::current();
  689. return in_array($name, $config->enabled_modules);
  690. }
  691. /**
  692. * Function: feather_enabled
  693. * Returns whether the given feather is enabled or not.
  694. *
  695. * Parameters:
  696. * $name - The folder name of the feather.
  697. *
  698. * Returns:
  699. * Whether or not the requested feather is enabled.
  700. */
  701. function feather_enabled($name) {
  702. $config = Config::current();
  703. return in_array($name, $config->enabled_feathers);
  704. }
  705. /**
  706. * Function: fallback
  707. * Sets a given variable if it is not set.
  708. *
  709. * The last of the arguments or the first non-empty value will be used.
  710. *
  711. * Parameters:
  712. * &$variable - The variable to return or set.
  713. *
  714. * Returns:
  715. * The value of whatever was chosen.
  716. */
  717. function fallback(&$variable) {
  718. if (is_bool($variable))
  719. return $variable;
  720. $set = (!isset($variable) or (is_string($variable) and trim($variable) === "") or $variable === array());
  721. $args = func_get_args();
  722. array_shift($args);
  723. if (count($args) > 1) {
  724. foreach ($args as $arg) {
  725. $fallback = $arg;
  726. if (isset($arg) and (!is_string($arg) or (is_string($arg) and trim($arg) !== "")) and $arg !== array())
  727. break;
  728. }
  729. } else
  730. $fallback = isset($args[0]) ? $args[0] : null ;
  731. if ($set)
  732. $variable = $fallback;
  733. return $set ? $fallback : $variable ;
  734. }
  735. /**
  736. * Function: oneof
  737. * Returns the first argument that is set and non-empty.
  738. *
  739. * It will guess where to stop based on the types of the arguments, e.g. "" has priority over array() but not 1.
  740. */
  741. function oneof() {
  742. $last = null;
  743. $args = func_get_args();
  744. foreach ($args as $index => $arg) {
  745. if (!isset($arg) or (is_string($arg) and trim($arg) === "") or $arg === array() or (is_object($arg) and empty($arg)) or ($arg === "0000-00-00 00:00:00"))
  746. $last = $arg;
  747. else
  748. return $arg;
  749. if ($index + 1 == count($args))
  750. break;
  751. $next = $args[$index + 1];
  752. $incomparable = ((is_array($arg) and !is_array($next)) or # This is a big check but it should cover most "incomparable" cases.
  753. (!is_array($arg) and is_array($next)) or # Using simple type comparison wouldn't work too well, for example
  754. (is_object($arg) and !is_object($next)) or # when "" would take priority over 1 in oneof("", 1) because they're
  755. (!is_object($arg) and is_object($next)) or # different types.
  756. (is_resource($arg) and !is_resource($next)) or
  757. (!is_resource($arg) and is_resource($next)));
  758. if (isset($arg) and isset($next) and $incomparable)
  759. return $arg;
  760. }
  761. return $last;
  762. }
  763. /**
  764. * Function: random
  765. * Returns a random string.
  766. *
  767. * Parameters:
  768. * $length - How long the string should be.
  769. * $specialchars - Use special characters in the resulting string?
  770. */
  771. function random($length, $specialchars = false) {
  772. $pattern = "1234567890abcdefghijklmnopqrstuvwxyz";
  773. if ($specialchars)
  774. $pattern.= "!@#$%^&*()?~";
  775. $len = strlen($pattern) - 1;
  776. $key = "";
  777. for($i = 0; $i < $length; $i++)
  778. $key.= $pattern[rand(0, $len)];
  779. return $key;
  780. }
  781. /**
  782. * Function: unique_filename
  783. * Makes a given filename unique for the uploads directory.
  784. *
  785. * Parameters:
  786. * $name - The name to check.
  787. * $path - Path to check in.
  788. * $num - Number suffix from which to start increasing if the filename exists.
  789. *
  790. * Returns:
  791. * A unique version of the given $name.
  792. */
  793. function unique_filename($name, $path = "", $num = 2) {
  794. $path = rtrim($path, "/");
  795. if (!file_exists(MAIN_DIR.Config::current()->uploads_path.$path."/".$name))
  796. return $name;
  797. $name = explode(".", $name);
  798. # Handle common double extensions
  799. foreach (array("tar.gz", "tar.bz", "tar.bz2") as $extension) {
  800. list($first, $second) = explode(".", $extension);
  801. $file_first =& $name[count($name) - 2];
  802. if ($file_first == $first and end($name) == $second) {
  803. $file_first = $first.".".$second;
  804. array_pop($name);
  805. }
  806. }
  807. $ext = ".".array_pop($name);
  808. $try = implode(".", $name)."-".$num.$ext;
  809. if (!file_exists(MAIN_DIR.Config::current()->uploads_path.$path."/".$try))
  810. return $try;
  811. return unique_filename(implode(".", $name).$ext, $path, $num + 1);
  812. }
  813. /**
  814. * Function: upload
  815. * Moves an uploaded file to the uploads directory.
  816. *
  817. * Parameters:
  818. * $file - The $_FILES value.
  819. * $extension - An array of valid extensions (case-insensitive).
  820. * $path - A sub-folder in the uploads directory (optional).
  821. * $put - Use copy() instead of move_uploaded_file()?
  822. *
  823. * Returns:
  824. * The resulting filename from the upload.
  825. */
  826. function upload($file, $extension = null, $path = "", $put = false) {
  827. $file_split = explode(".", $file['name']);
  828. $path = rtrim($path, "/");
  829. $dir = MAIN_DIR.Config::current()->uploads_path.$path;
  830. if (!file_exists($dir))
  831. mkdir($dir, 0777, true);
  832. $original_ext = end($file_split);
  833. # Handle common double extensions
  834. foreach (array("tar.gz", "tar.bz", "tar.bz2") as $ext) {
  835. list($first, $second) = explode(".", $ext);
  836. $file_first =& $file_split[count($file_split) - 2];
  837. if ($file_first == $first and end($file_split) == $second) {
  838. $file_first = $first.".".$second;
  839. array_pop($file_split);
  840. }
  841. }
  842. $file_ext = end($file_split);
  843. if (is_array($extension)) {
  844. if (!in_array(strtolower($file_ext), $extension) and !in_array(strtolower($original_ext), $extension)) {
  845. $list = "";
  846. for ($i = 0; $i < count($extension); $i++) {
  847. $comma = "";
  848. if (($i + 1) != count($extension)) $comma = ", ";
  849. if (($i + 2) == count($extension)) $comma = ", and ";
  850. $list.= "<code>*.".$extension[$i]."</code>".$comma;
  851. }
  852. error(__("Invalid Extension"), _f("Only %s files are accepted.", array($list)));
  853. }
  854. } elseif (isset($extension) and
  855. strtolower($file_ext) != strtolower($extension) and
  856. strtolower($original_ext) != strtolower($extension))
  857. error(__("Invalid Extension"), _f("Only %s files are supported.", array("*.".$extension)));
  858. array_pop($file_split);
  859. $file_clean = implode(".", $file_split);
  860. $file_clean = sanitize($file_clean, false).".".$file_ext;
  861. $filename = unique_filename($file_clean, $path);
  862. $message = __("Couldn't upload file. CHMOD <code>".$dir."</code> to 777 and try again. If this problem persists, it's probably timing out; in which case, you must contact your system administrator to increase the maximum POST and upload sizes.");
  863. if ($put) {
  864. if (!@copy($file['tmp_name'], $dir."/".$filename))
  865. error(__("Error"), $message);
  866. } elseif (!@move_uploaded_file($file['tmp_name'], $dir."/".$filename))
  867. error(__("Error"), $message);
  868. return ($path ? $path."/".$filename : $filename);
  869. }
  870. /**
  871. * Function: upload_from_url
  872. * Copy a file from a specified URL to their upload directory.
  873. *
  874. * Parameters:
  875. * $url - The URL to copy.
  876. * $extension - An array of valid extensions (case-insensitive).
  877. * $path - A sub-folder in the uploads directory (optional).
  878. *
  879. * See Also:
  880. * <upload>
  881. */
  882. function upload_from_url($url, $extension = null, $path = "") {
  883. $file = tempnam(null, "chyrp");
  884. file_put_contents($file, get_remote($url));
  885. $fake_file = array("name" => basename(parse_url($url, PHP_URL_PATH)),
  886. "tmp_name" => $file);
  887. return upload($fake_file, $extension, $path, true);
  888. }
  889. /**
  890. * Function: uploaded
  891. * Returns a URL to an uploaded file.
  892. *
  893. * Parameters:
  894. * $file - Filename relative to the uploads directory.
  895. */
  896. function uploaded($file, $url = true) {
  897. if (empty($file))
  898. return "";
  899. $config = Config::current();
  900. return ($url ? $config->chyrp_url.$config->uploads_path.$file : MAIN_DIR.$config->uploads_path.$file);
  901. }
  902. /**
  903. * Function: timer_start
  904. * Starts the timer.
  905. */
  906. function timer_start() {
  907. global $time_start;
  908. $mtime = explode(" ", microtime());
  909. $mtime = $mtime[1] + $mtime[0];
  910. $time_start = $mtime;
  911. }
  912. /**
  913. * Function: timer_stop
  914. * Stops the timer and returns the total time.
  915. *
  916. * Parameters:
  917. * $precision - Number of decimals places to round to.
  918. *
  919. * Returns:
  920. * A formatted number with the given $precision.
  921. */
  922. function timer_stop($precision = 3) {
  923. global $time_start;
  924. $mtime = microtime();
  925. $mtime = explode(" ", $mtime);
  926. $mtime = $mtime[1] + $mtime[0];
  927. $time_end = $mtime;
  928. $time_total = $time_end - $time_start;
  929. return number_format($time_total, $precision);
  930. }
  931. /**
  932. * Function: normalize
  933. * Attempts to normalize all newlines and whitespace into single spaces.
  934. *
  935. * Returns:
  936. * The normalized string.
  937. */
  938. function normalize($string) {
  939. $trimmed = trim($string);
  940. $newlines = str_replace("\n\n", " ", $trimmed);
  941. $newlines = str_replace("\n", "", $newlines);
  942. $normalized = preg_replace("/[\s\n\r\t]+/", " ", $newlines);
  943. return $normalized;
  944. }
  945. /**
  946. * Function: get_remote
  947. * Grabs the contents of a website/location.
  948. *
  949. * Parameters:
  950. * $url - The URL of the location to grab.
  951. *
  952. * Returns:
  953. * The response from the remote URL.
  954. */
  955. function get_remote($url) {
  956. extract(parse_url($url), EXTR_SKIP);
  957. if (ini_get("allow_url_fopen")) {
  958. $content = @file_get_contents($url);
  959. if ($http_response_header[0] != "HTTP/1.1 200 OK")
  960. $content = "Server returned a message: $http_response_header[0]";
  961. } elseif (function_exists("curl_init")) {
  962. $handle = curl_init();
  963. curl_setopt($handle, CURLOPT_URL, $url);
  964. curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 1);
  965. curl_setopt($handle, CURLOPT_RETURNTRANSFER, 1);
  966. curl_setopt($handle, CURLOPT_TIMEOUT, 60);
  967. $content = curl_exec($handle);
  968. $status = curl_getinfo($handle, CURLINFO_HTTP_CODE);
  969. curl_close($handle);
  970. if ($status != 200)
  971. $content = "Server returned a message: $status";
  972. } else {
  973. $path = (!isset($path)) ? '/' : $path ;
  974. if (isset($query)) $path.= '?'.$query;
  975. $port = (isset($port)) ? $port : 80 ;
  976. $connect = @fsockopen($host, $port, $errno, $errstr, 2);
  977. if (!$connect) return false;
  978. # Send the GET headers
  979. fwrite($connect, "GET ".$path." HTTP/1.1\r\n");
  980. fwrite($connect, "Host: ".$host."\r\n");
  981. fwrite($connect, "User-Agent: Chyrp/".CHYRP_VERSION."\r\n\r\n");
  982. $content = "";
  983. while (!feof($connect)) {
  984. $line = fgets($connect, 128);
  985. if (preg_match("/\r\n/", $line)) continue;
  986. $content.= $line;
  987. }
  988. fclose($connect);
  989. }
  990. return $content;
  991. }
  992. /**
  993. * Function: self_url
  994. * Returns the current URL.
  995. */
  996. function self_url() {
  997. $split = explode("/", $_SERVER['SERVER_PROTOCOL']);
  998. $protocol = strtolower($split[0]);
  999. $default_port = ($protocol == "http") ? 80 : 443 ;
  1000. $port = ($_SERVER['SERVER_PORT'] == $default_port) ? "" : ":".$_SERVER['SERVER_PORT'] ;
  1001. return $protocol."://".$_SERVER['SERVER_NAME'].$port.$_SERVER['REQUEST_URI'];
  1002. }
  1003. /**
  1004. * Function: show_404
  1005. * Shows a 404 error message and immediately exits.
  1006. *
  1007. * Parameters:
  1008. * $scope - An array of values to extract into the scope.
  1009. */
  1010. function show_404() {
  1011. header("HTTP/1.1 404 Not Found");
  1012. if (!defined('CHYRP_VERSION'))
  1013. exit("404 Not Found");
  1014. $theme = Theme::current();
  1015. $main = MainController::current();
  1016. Trigger::current()->call("not_found");
  1017. if ($theme->file_exists("pages/404"))
  1018. $main->display("pages/404", array(), "404");
  1019. else
  1020. error(__("404 Not Found"), __("The requested page could not be located."));
  1021. exit;
  1022. }
  1023. /**
  1024. * Function: set_locale
  1025. * Set locale in a platform-independent way
  1026. *
  1027. * Parameters:
  1028. * $locale - the locale name (@en_US@, @uk_UA@, @fr_FR@ etc.)
  1029. *
  1030. * Returns:
  1031. * The encoding name used by locale-aware functions.
  1032. */
  1033. function set_locale($locale) { # originally via http://www.onphp5.com/article/22; heavily modified
  1034. if ($locale == "en_US") return; # en_US is the default in Chyrp; their system may have
  1035. # its own locale setting and no Chyrp translation available
  1036. # for their locale, so let's just leave it alone.
  1037. list($lang, $cty) = explode("_", $locale);
  1038. $locales = array($locale.".UTF-8", $lang, "en_US.UTF-8", "en");
  1039. $result = setlocale(LC_ALL, $locales);
  1040. return (!strpos($result, 'UTF-8')) ? "CP".preg_replace('~\.(\d+)$~', "\\1", $result) : "UTF-8" ;
  1041. }
  1042. /**
  1043. * Function: sanitize_input
  1044. * Makes sure no inherently broken ideas such as magic_quotes break our application
  1045. *
  1046. * Parameters:
  1047. * $data - The array to be sanitized, usually one of @$_GET@, @$_POST@, @$_COOKIE@, or @$_REQUEST@
  1048. */
  1049. function sanitize_input(&$data) {
  1050. foreach ($data as &$value)
  1051. if (is_array($value))
  1052. sanitize_input($value);
  1053. else
  1054. $value = get_magic_quotes_gpc() ? stripslashes($value) : $value ;
  1055. }
  1056. /**
  1057. * Function: match
  1058. * Try to match a string against an array of regular expressions.
  1059. *
  1060. * Parameters:
  1061. * $try - An array of regular expressions, or a single regular expression.
  1062. * $haystack - The string to test.
  1063. *
  1064. * Returns:
  1065. * Whether or not the match succeeded.
  1066. */
  1067. function match($try, $haystack) {
  1068. if (is_string($try))
  1069. return (bool) preg_match($try, $haystack);
  1070. foreach ($try as $needle)
  1071. if (preg_match($needle, $haystack))
  1072. return true;
  1073. return false;
  1074. }
  1075. /**
  1076. * Function: cancel_module
  1077. * Temporarily removes a module from $config->enabled_modules.
  1078. *
  1079. * Parameters:
  1080. * $target - Module name to disable.
  1081. */
  1082. function cancel_module($target) {
  1083. $this_disabled = array();
  1084. if (isset(Modules::$instances[$target]))
  1085. Modules::$instances[$target]->cancelled = true;
  1086. $config = Config::current();
  1087. foreach ($config->enabled_modules as $module)
  1088. if ($module != $target)
  1089. $this_disabled[] = $module;
  1090. return $config->enabled_modules = $this_disabled;
  1091. }
  1092. /**
  1093. * Function: time_in_timezone
  1094. * Returns the appropriate time() for representing a timezone.
  1095. */
  1096. function time_in_timezone($timezone) {
  1097. $orig = get_timezone();
  1098. set_timezone($timezone);
  1099. $time = date("F jS, Y, g:i A");
  1100. set_timezone($orig);
  1101. return strtotime($time);
  1102. }
  1103. /**
  1104. * Function: timezones
  1105. * Returns an array of timezones that have unique offsets. Doesn't count deprecated timezones.
  1106. */
  1107. function timezones() {
  1108. $zones = array();
  1109. $deprecated = array("Brazil/Acre", "Brazil/DeNoronha", "Brazil/East", "Brazil/West", "Canada/Atlantic", "Canada/Central", "Canada/East-Saskatchewan", "Canada/Eastern", "Canada/Mountain", "Canada/Newfoundland", "Canada/Pacific", "Canada/Saskatchewan", "Canada/Yukon", "CET", "Chile/Continental", "Chile/EasterIsland", "CST6CDT", "Cuba", "EET", "Egypt", "Eire", "EST", "EST5EDT", "Etc/GMT", "Etc/GMT+0", "Etc/GMT+1", "Etc/GMT+10", "Etc/GMT+11", "Etc/GMT+12", "Etc/GMT+2", "Etc/GMT+3", "Etc/GMT+4", "Etc/GMT+5", "Etc/GMT+6", "Etc/GMT+7", "Etc/GMT+8", "Etc/GMT+9", "Etc/GMT-0", "Etc/GMT-1", "Etc/GMT-10", "Etc/GMT-11", "Etc/GMT-12", "Etc/GMT-13", "Etc/GMT-14", "Etc/GMT-2", "Etc/GMT-3", "Etc/GMT-4", "Etc/GMT-5", "Etc/GMT-6", "Etc/GMT-7", "Etc/GMT-8", "Etc/GMT-9", "Etc/GMT0", "Etc/Greenwich", "Etc/UCT", "Etc/Universal", "Etc/UTC", "Etc/Zulu", "Factory", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0", "Greenwich", "Hongkong", "HST", "Iceland", "Iran", "Israel", "Jamaica", "Japan", "Kwajalein", "Libya", "MET", "Mexico/BajaNorte", "Mexico/BajaSur", "Mexico/General", "MST", "MST7MDT", "Navajo", "NZ", "NZ-CHAT", "Poland", "Portugal", "PRC", "PST8PDT", "ROC", "ROK", "Singapore", "Turkey", "UCT", "Universal", "US/Alaska", "US/Aleutian", "US/Arizona", "US/Central", "US/East-Indiana", "US/Eastern", "US/Hawaii", "US/Indiana-Starke", "US/Michigan", "US/Mountain", "US/Pacific", "US/Pacific-New", "US/Samoa", "UTC", "W-SU", "WET", "Zulu");
  1110. foreach (timezone_identifiers_list() as $zone)
  1111. if (!in_array($zone, $deprecated))
  1112. $zones[] = array("name" => $zone,
  1113. "now" => time_in_timezone($zone));
  1114. function by_time($a, $b) {
  1115. return (int) ($a["now"] > $b["now"]);
  1116. }
  1117. usort($zones, "by_time");
  1118. return $zones;
  1119. }
  1120. /**
  1121. * Function: set_timezone
  1122. * Sets the timezone.
  1123. *
  1124. * Parameters:
  1125. * $timezone - The timezone to set.
  1126. */
  1127. function set_timezone($timezone) {
  1128. if (function_exists("date_default_timezone_set"))
  1129. date_default_timezone_set($timezone);
  1130. else
  1131. ini_set("date.timezone", $timezone);
  1132. }
  1133. /**
  1134. * Function: get_timezone()
  1135. * Returns the current timezone.
  1136. */
  1137. function get_timezone() {
  1138. if (function_exists("date_default_timezone_set"))
  1139. return date_default_timezone_get();
  1140. else
  1141. return ini_get("date.timezone");
  1142. }
  1143. /**
  1144. * Function: error_panicker
  1145. * Exits and states where the error occurred.
  1146. */
  1147. function error_panicker($errno, $message, $file, $line) {
  1148. if (error_reporting() === 0)
  1149. return; # Suppressed error.
  1150. exit("ERROR: ".$message." (".$file." on line ".$line.")");
  1151. }
  1152. /**
  1153. * Function: keywords
  1154. * Handle keyword-searching.
  1155. *
  1156. * Parameters:
  1157. * $query - The query to parse.
  1158. * $plain - WHERE syntax to search for non-keyword queries.
  1159. * $table - If specified, the keywords will be checked against this table's columns for validity.
  1160. *
  1161. * Returns:
  1162. * An array containing the "WHERE" queries and the corresponding parameters.
  1163. */
  1164. function keywords($query, $plain, $table = null) {
  1165. if (!trim($query))
  1166. return array(array(), array());
  1167. $search = array();
  1168. $matches = array();
  1169. $where = array();
  1170. $params = array();
  1171. if ($table)
  1172. $columns = SQL::current()->select($table)->fetch();
  1173. $queries = explode(" ", $query);
  1174. foreach ($queries as $query)
  1175. if (!preg_match("/([a-z0-9_]+):(.+)/", $query))
  1176. $search[] = $query;
  1177. else
  1178. $matches[] = $query;
  1179. $times = array("year", "month", "day", "hour", "minute", "second");
  1180. foreach ($matches as $match) {
  1181. list($test, $equals,) = explode(":", $match);
  1182. if ($equals[0] == '"') {
  1183. if (substr($equals, -1) != '"')
  1184. foreach ($search as $index => $part) {
  1185. $equals.= " ".$part;
  1186. unset($search[$index]);
  1187. if (substr($part, -1) == '"')
  1188. break;
  1189. }
  1190. $equals = ltrim(trim($equals, '"'), '"');
  1191. }
  1192. if (in_array($test, $times)) {
  1193. if ($equals == "today")
  1194. $where["created_at like"] = date("%Y-m-d %");
  1195. elseif ($equals == "yesterday")
  1196. $where["created_at like"] = date("%Y-m-d %", now("-1 day"));
  1197. elseif ($equals == "tomorrow")
  1198. error(__("Error"), "Unfortunately our flux capacitor is currently having issues. Try again yesterday.");
  1199. else
  1200. $where[strtoupper($test)."(created_at)"] = $equals;
  1201. } elseif ($test == "author") {
  1202. $user = new User(array("login" => $equals));
  1203. if ($user->no_results and $equals == "me") {
  1204. !($table == "users") ? $where["user_id"] = Visitor::current()->id : $where["id"] = Visitor::current()->id;
  1205. } else
  1206. !($table == "users") ? $where["user_id"] = $user->id : $where["id"] = $user->id;
  1207. } elseif ($test == "group") {
  1208. $group = new Group(array("name" => $equals));
  1209. $where["group_id"] = $equals = ($group->no_results) ? 0 : $group->id;
  1210. } else
  1211. $where[$test] = $equals;
  1212. }
  1213. if ($table)
  1214. foreach ($where as $col => $val)
  1215. if (!isset($where[$col])) {
  1216. if ($table == "posts") {
  1217. $where["post_attributes.name"] = $col;
  1218. $where["post_attributes.value like"] = "%".$val."%";
  1219. }
  1220. unset($where[$col]);
  1221. }
  1222. if (!empty($search)) {
  1223. $where[] = $plain;
  1224. $params[":query"] = "%".join(" ", $search)."%";
  1225. }
  1226. $keywords = array($where, $params);
  1227. Trigger::current()->filter($keywords, "keyword_search", $query, $plain);
  1228. return $keywords;
  1229. }
  1230. /**
  1231. * Function: init_extensions
  1232. * Initialize all Modules and Feathers.
  1233. */
  1234. function init_extensions() {
  1235. $config = Config::current();
  1236. # Instantiate all Modules.
  1237. foreach ($config->enabled_modules as $index => $module) {
  1238. if (!file_exists(MODULES_DIR."/".$module."/".$module.".php")) {
  1239. unset($config->enabled_modules[$index]);
  1240. continue;
  1241. }
  1242. if (file_exists(MODULES_DIR."/".$module."/locale/".$config->locale.".mo"))
  1243. load_translator($module, MODULES_DIR."/".$module."/locale/".$config->locale.".mo");
  1244. require MODULES_DIR."/".$module."/".$module.".php";
  1245. $camelized = camelize($module);
  1246. if (!class_exists($camelized))
  1247. continue;
  1248. Modules::$instances[$module] = new $camelized;
  1249. Modules::$instances[$module]->safename = $module;
  1250. foreach (YAML::load(MODULES_DIR."/".$module."/info.yaml") as $key => $val)
  1251. Modules::$instances[$module]->$key = (is_string($val)) ? __($val, $module) : $val ;
  1252. }
  1253. # Instantiate all Feathers.
  1254. foreach ($config->enabled_feathers as $index => $feather) {
  1255. if (!file_exists(FEATHERS_DIR."/".$feather."/".$feather.".php")) {
  1256. unset($config->enabled_feathers[$index]);
  1257. continue;
  1258. }
  1259. if (file_exists(FEATHERS_DIR."/".$feather."/locale/".$config->locale.".mo"))
  1260. load_translator($feather, FEATHERS_DIR."/".$feather."/locale/".$config->locale.".mo");
  1261. require FEATHERS_DIR."/".$feather."/".$feather.".php";
  1262. $camelized = camelize($feather);
  1263. if (!class_exists($camelized))
  1264. continue;
  1265. Feathers::$instances[$feather] = new $camelized;
  1266. Feathers::$instances[$feather]->safename = $feather;
  1267. foreach (YAML::load(FEATHERS_DIR."/".$feather."/info.yaml") as $key => $val)
  1268. Feathers::$instances[$feather]->$key = (is_string($val)) ? __($val, $feather) : $val ;
  1269. }
  1270. # Initialize all modules.
  1271. foreach (Feathers::$instances as $feather)
  1272. if (method_exists($feather, "__init"))
  1273. $feather->__init();
  1274. foreach (Modules::$instances as $module)
  1275. if (method_exists($module, "__init"))
  1276. $module->__init();
  1277. }
  1278. /**
  1279. * Function: xml2arr
  1280. * Recursively converts a SimpleXML object (and children) to an array.
  1281. *
  1282. * Parameters:
  1283. * $parse - The SimpleXML object to convert into an array.
  1284. */
  1285. function xml2arr($parse) {
  1286. if (empty($parse))
  1287. return "";
  1288. $parse = (array) $parse;
  1289. foreach ($parse as &$val)
  1290. if (get_class($val) == "SimpleXMLElement")
  1291. $val = xml2arr($val);
  1292. return $parse;
  1293. }
  1294. /**
  1295. * Function: arr2xml
  1296. * Recursively adds an array (or object I guess) to a SimpleXML object.
  1297. *
  1298. * Parameters:
  1299. * &$object - The SimpleXML object to modify.
  1300. * $data - The data to add to the SimpleXML object.
  1301. */
  1302. function arr2xml(&$object, $data) {
  1303. foreach ($data as $key => $val) {
  1304. if (is_int($key) and (empty($val) or (is_string($val) and trim($val) == ""))) {
  1305. unset($data[$key]);
  1306. continue;
  1307. }
  1308. if (is_array($val)) {
  1309. if (in_array(0, array_keys($val))) { # Numeric-indexed things need to be added as duplicates
  1310. foreach ($val as $dup) {
  1311. $xml = $object->addChild($key);
  1312. arr2xml($xml, $dup);
  1313. }
  1314. } else {
  1315. $xml = $object->addChild($key);
  1316. arr2xml($xml, $val);
  1317. }
  1318. } else
  1319. $object->addChild($key, fix($val, false, false));
  1320. }
  1321. }
  1322. /**
  1323. * Function: relative_time
  1324. * Returns the difference between the given timestamps or now.
  1325. *
  1326. * Parameters:
  1327. * $time - Timestamp to compare to.
  1328. * $from - Timestamp to compare from. If not specified, defaults to now.
  1329. *
  1330. * Returns:
  1331. * A string formatted like "3 days ago" or "3 days from now".
  1332. */
  1333. function relative_time($when, $from = null) {
  1334. fallback($from, time());
  1335. $time = (is_numeric($when)) ? $when : strtotime($when) ;
  1336. $difference = $from - $time;
  1337. if ($difference < 0) {
  1338. $word = "from now";
  1339. $difference = -$difference;
  1340. } elseif ($difference > 0)
  1341. $word = "ago";
  1342. else
  1343. return "just now";
  1344. $units = array("second" => 1,
  1345. "minute" => 60,
  1346. "hour" => 60 * 60,
  1347. "day" => 60 * 60 * 24,
  1348. "week" => 60 * 60 * 24 * 7,
  1349. "month" => 60 * 60 * 24 * 30,
  1350. "year" => 60 * 60 * 24 * 365,
  1351. "decade" => 60 * 60 * 24 * 365 * 10,
  1352. "century" => 60 * 60 * 24 * 365 * 100,
  1353. "millennium" => 60 * 60 * 24 * 365 * 1000);
  1354. $possible_units = array();
  1355. foreach ($units as $name => $val)
  1356. if (($name == "week" and $difference >= ($val * 2)) or # Only say "weeks" after two have passed.
  1357. ($name != "week" and $difference >= $val))
  1358. $unit = $possible_units[] = $name;
  1359. $precision = (int) in_array("year", $possible_units);
  1360. $amount = round($difference / $units[$unit], $precision);
  1361. return $amount." ".pluralize($unit, $amount)." ".$word;
  1362. }
  1363. /**
  1364. * Function: list_notate
  1365. * Notates an array as a list of things.
  1366. *
  1367. * Parameters:
  1368. * $array - An array of things to notate.
  1369. * $quotes - Wrap quotes around strings?
  1370. *
  1371. * Returns:
  1372. * A string like "foo, bar, and baz".
  1373. */
  1374. function list_notate($array, $quotes = false) {
  1375. $count = 0;
  1376. $items = array();
  1377. foreach ($array as $item) {
  1378. $string = (is_string($item) and $quotes) ? "&#8220;".$item."&#8221;" : $item ;
  1379. if (count($array) == ++$count and $count !== 1)
  1380. $items[] = __("and ").$string;
  1381. else
  1382. $items[] = $string;
  1383. }
  1384. return (count($array) == 2) ? implode(" ", $items) : implode(", ", $items) ;
  1385. }
  1386. /**
  1387. * Function: email
  1388. * Send an email. Function arguments are exactly the same as the PHP mail() function.
  1389. *
  1390. * This is intended so that modules can provide an email method if the server cannot use mail().
  1391. */
  1392. function email() {
  1393. $function = "mail";
  1394. Trigger::current()->filter($function, "send_mail");
  1395. $args = func_get_args(); # Looks redundant, but it must be so in order to meet PHP's retardation requirements.
  1396. return call_user_func_array($function, $args);
  1397. }
  1398. /**
  1399. * Function: now
  1400. * Alias to strtotime, for prettiness like now("+1 day").
  1401. */
  1402. function now($when) {
  1403. return strtotime($when);
  1404. }
  1405. /**
  1406. * Function: comma_sep
  1407. * Convert a comma-seperated string into an array of the listed values.
  1408. */
  1409. function comma_sep($string) {
  1410. $commas = explode(",", $string);
  1411. $trimmed = array_map("trim", $commas);
  1412. $cleaned = array_diff(array_unique($trimmed), array(""));
  1413. return $cleaned;
  1414. }