OSDN Git Service

sync the original code
[nucleus-jp/nucleus-jp-ancient.git] / utf8 / nucleus / libs / globalfunctions.php
1 <?php\r
2 \r
3 /**\r
4   * Nucleus: PHP/MySQL Weblog CMS (http://nucleuscms.org/)\r
5   * Copyright (C) 2002-2005 The Nucleus Group\r
6   *\r
7   * This program is free software; you can redistribute it and/or\r
8   * modify it under the terms of the GNU General Public License\r
9   * as published by the Free Software Foundation; either version 2\r
10   * of the License, or (at your option) any later version.\r
11   * (see nucleus/documentation/index.html#license for more info)\r
12   *\r
13   * $Id: globalfunctions.php,v 1.6 2005-08-13 07:27:42 kimitake Exp $\r
14   * $NucleusJP: globalfunctions.php,v 1.5 2005/03/19 09:05:40 kimitake Exp $\r
15   */\r
16 \r
17 // needed if we include globalfunctions from install.php\r
18 global $nucleus, $CONF, $DIR_LIBS, $DIR_LANG, $manager, $member; \r
19 \r
20 \r
21 checkVars(array('nucleus', 'CONF', 'DIR_LIBS', 'MYSQL_HOST', 'MYSQL_USER', 'MYSQL_PASSWORD', 'MYSQL_DATABASE', 'DIR_LANG', 'DIR_PLUGINS'));\r
22 \r
23 $CONF['debug'] = 0;\r
24 \r
25 $nucleus['version'] = 'v3.21';\r
26 if (getNucleusPatchLevel() > 0)\r
27 {\r
28         $nucleus['version'] .= '/' . getNucleusPatchLevel();\r
29 }\r
30 \r
31 /*\r
32         Indicates when Nucleus should display startup errors. Set to 1 if you want\r
33         the error enabled (default), false otherwise\r
34 \r
35         alertOnHeadersSent\r
36                 Displays an error when visiting a public Nucleus page and headers have\r
37                 been sent out to early. This usually indicates an error in either a\r
38                 configuration file or a language file, and could cause Nucleus to\r
39                 malfunction\r
40         alertOnSecurityRisk\r
41                 Displays an error only when visiting the admin area, and when one or\r
42                 more of the installation files (install.php, install.sql, upgrades/\r
43                 directory) are still on the server.\r
44 */\r
45 $CONF['alertOnHeadersSent'] = 1;\r
46 $CONF['alertOnSecurityRisk'] = 1;\r
47 \r
48 /**\r
49   * returns the currently used version (100 = 1.00, 101 = 1.01, etc...)\r
50   */\r
51 function getNucleusVersion() {\r
52         return 321;\r
53 }\r
54 \r
55 /**\r
56  * power users can install patches in between nucleus releases. These patches\r
57  * usually add new functionality in the plugin API and allow those to\r
58  * be tested without having to install CVS.\r
59  */\r
60 function getNucleusPatchLevel() {\r
61         return 0;\r
62 }\r
63 \r
64 \r
65 if ($CONF['debug']) {\r
66         error_reporting(E_ALL & ~E_NOTICE);     // report almost all errors!\r
67                                                                                 // (no uninitialized vars and such)\r
68 } else {\r
69         error_reporting(E_ERROR | E_WARNING | E_PARSE);\r
70 }\r
71 \r
72 // we will use postVar, getVar, ... methods instead of HTTP_GET_VARS or _GET\r
73 if ($CONF['installscript']!=1){ // vars were already included in install.php\r
74   if (phpversion() >= '4.1.0')\r
75           include_once($DIR_LIBS . 'vars4.1.0.php');\r
76   else\r
77           include_once($DIR_LIBS . 'vars4.0.6.php');\r
78 }\r
79 \r
80 function intPostVar($name) { return intval(postVar($name));}\r
81 function intGetVar($name) { return intval(getVar($name));}\r
82 function intRequestVar($name) { return intval(requestVar($name)); }\r
83 function intCookieVar($name) { return intval(cookieVar($name)); }\r
84 \r
85 // get all variables that can come from the request and put them in the global scope\r
86 $blogid                 = requestVar('blogid');\r
87 $itemid                 = intRequestVar('itemid');\r
88 $catid                  = intRequestVar('catid');\r
89 $skinid                 = requestVar('skinid');\r
90 $memberid               = requestVar('memberid');\r
91 $archivelist    = requestVar('archivelist');\r
92 $imagepopup             = requestVar('imagepopup');\r
93 $archive                = requestVar('archive');\r
94 $query                  = requestVar('query');\r
95 $highlight              = requestVar('highlight');\r
96 $amount                 = requestVar('amount');\r
97 $action                 = requestVar('action');\r
98 $nextaction             = requestVar('nextaction');\r
99 $maxresults     = requestVar('maxresults');\r
100 $startpos       = intRequestVar('startpos');\r
101 $errormessage   = '';\r
102 $error                  = '';\r
103 \r
104 if (!headers_sent())\r
105         header('Generator: Nucleus CMS ' . $nucleus['version']);\r
106 \r
107 // include core classes that are needed for login & plugin handling\r
108 include($DIR_LIBS . 'MEMBER.php');\r
109 include($DIR_LIBS . 'ACTIONLOG.php');\r
110 include($DIR_LIBS . 'MANAGER.php');\r
111 include($DIR_LIBS . 'PLUGIN.php');\r
112 \r
113 $manager =& MANAGER::instance();\r
114 \r
115 // make sure there's no unnecessary escaping:\r
116 set_magic_quotes_runtime(0);\r
117 \r
118 // only needed when updating logs\r
119 if ($CONF['UsingAdminArea']) {\r
120         include($DIR_LIBS . 'xmlrpc.inc.php');  // XML-RPC client classes\r
121         include_once($DIR_LIBS . 'ADMIN.php');\r
122 }\r
123 \r
124 \r
125 // connect to sql\r
126 sql_connect();\r
127 \r
128 // makes sure database connection gets closed on script termination\r
129 register_shutdown_function('sql_disconnect');\r
130 \r
131 // read config\r
132 getConfig();\r
133 \r
134 // automatically use simpler toolbar for mozilla\r
135 if (($CONF['DisableJsTools'] == 0) && strstr(serverVar('HTTP_USER_AGENT'),'Mozilla/5.0') && strstr(serverVar('HTTP_USER_AGENT'),'Gecko'))\r
136         $CONF['DisableJsTools'] = 2;\r
137 \r
138 // login if cookies set\r
139 \r
140 $member =& new MEMBER();\r
141 \r
142 // login/logout when required or renew cookies\r
143 if ($action == 'login') {\r
144         // Form Authentication\r
145         $login  = postVar('login');\r
146         $pw     = postVar('password');\r
147         $shared = intPostVar('shared'); // shared computer or not\r
148 \r
149         if ($member->login($login,$pw)) {\r
150 \r
151                 $member->newCookieKey();\r
152                 $member->setCookies($shared);\r
153 \r
154                 // allows direct access to parts of the admin area after logging in\r
155                 if ($nextaction)\r
156                         $action = $nextaction;\r
157 \r
158                 $manager->notify('LoginSuccess',array('member' => &$member));\r
159                 ACTIONLOG::add(INFO, "Login successful for $login (sharedpc=$shared)");\r
160         } else {\r
161                 $manager->notify('LoginFailed',array('username' => $login));\r
162                 ACTIONLOG::add(INFO, 'Login failed for ' . $login);\r
163         }\r
164 /*\r
165 \r
166 Backed out for now: See http://forum.nucleuscms.org/viewtopic.php?t=3684 for details\r
167 \r
168 } elseif (serverVar('PHP_AUTH_USER') && serverVar('PHP_AUTH_PW')) {\r
169         // HTTP Authentication\r
170        $login  = serverVar('PHP_AUTH_USER');\r
171        $pw     = serverVar('PHP_AUTH_PW');\r
172 \r
173        if ($member->login($login,$pw)) {\r
174                $manager->notify('LoginSuccess',array('member' => &$member));\r
175                ACTIONLOG::add(INFO, "HTTP authentication successful for $login");\r
176        } else {\r
177                $manager->notify('LoginFailed',array('username' => $login));\r
178                ACTIONLOG::add(INFO, 'HTTP authentication failed for ' . $login);\r
179 \r
180                //Since bad credentials, generate an apropriate error page\r
181                header("WWW-Authenticate: Basic realm=\"Nucleus CMS {$nucleus['version']}\"");\r
182                header('HTTP/1.0 401 Unauthorized');\r
183                echo 'Invalid username or password';\r
184                exit;\r
185        }\r
186 */\r
187 \r
188 } elseif (($action == 'logout') && (!headers_sent()) && cookieVar($CONF['CookiePrefix'] . 'user')){\r
189         // remove cookies on logout\r
190         setcookie($CONF['CookiePrefix'] .'user','',(time()-2592000),$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);\r
191         setcookie($CONF['CookiePrefix'] .'loginkey','',(time()-2592000),$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);\r
192         $manager->notify('Logout',array('username' => cookieVar($CONF['CookiePrefix'] .'user')));\r
193 } elseif (cookieVar($CONF['CookiePrefix'] .'user')) {\r
194         // Cookie Authentication\r
195         $res = $member->cookielogin(cookieVar($CONF['CookiePrefix'] .'user'), cookieVar($CONF['CookiePrefix'] .'loginkey'));\r
196 \r
197         // renew cookies when not on a shared computer\r
198         if ($res && (cookieVar($CONF['CookiePrefix'] .'sharedpc') != 1) && (!headers_sent()))\r
199                 $member->setCookies();\r
200 }\r
201 \r
202 // login completed\r
203 $manager->notify('PostAuthentication',array('loggedIn' => $member->isLoggedIn()));\r
204 \r
205 // first, let's see if the site is disabled or not\r
206 if ($CONF['DisableSite'] && !$member->isAdmin()) {\r
207         redirect($CONF['DisableSiteURL']);\r
208         exit;\r
209 }\r
210 \r
211 // load other classes\r
212 include($DIR_LIBS . 'PARSER.php');\r
213 include($DIR_LIBS . 'SKIN.php');\r
214 include($DIR_LIBS . 'TEMPLATE.php');\r
215 include($DIR_LIBS . 'BLOG.php');\r
216 include($DIR_LIBS . 'COMMENTS.php');\r
217 include($DIR_LIBS . 'COMMENT.php');\r
218 //include($DIR_LIBS . 'ITEM.php');\r
219 include($DIR_LIBS . 'NOTIFICATION.php');\r
220 include($DIR_LIBS . 'BAN.php');\r
221 include($DIR_LIBS . 'PAGEFACTORY.php');\r
222 include($DIR_LIBS . 'SEARCH.php');\r
223 \r
224 \r
225 // set lastVisit cookie (if allowed)\r
226 if (!headers_sent()) {\r
227         if ($CONF['LastVisit'])\r
228                 setcookie($CONF['CookiePrefix'] .'lastVisit',time(),time()+2592000,$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);\r
229         else\r
230                 setcookie($CONF['CookiePrefix'] .'lastVisit','',(time()-2592000),$CONF['CookiePath'],$CONF['CookieDomain'],$CONF['CookieSecure']);\r
231 }\r
232 \r
233 // read language file, only after user has been initialized\r
234 $language = getLanguageName();\r
235 include($DIR_LANG . ereg_replace( '[\\|/]', '', $language) . '.php');\r
236 \r
237 /*\r
238         Backed out for now: See http://forum.nucleuscms.org/viewtopic.php?t=3684 for details\r
239         \r
240 // To remove after v2.5 is released and language files have been updated. \r
241 // Including this makes sure that language files for v2.5beta can still be used for v2.5final\r
242 // without having weird _SETTINGS_EXTAUTH string showing up in the admin area.\r
243 if (!defined('_MEMBERS_BYPASS'))\r
244 {\r
245         define('_SETTINGS_EXTAUTH',                     'Enable External Authentication');\r
246         define('_WARNING_EXTAUTH',                      'Warning: Enable only if needed.');\r
247         define('_MEMBERS_BYPASS',                       'Use External Authentication');\r
248 }\r
249 \r
250 */\r
251 \r
252 // make sure the archivetype skinvar keeps working when _ARCHIVETYPE_XXX not defined\r
253 if (!defined('_ARCHIVETYPE_MONTH'))\r
254 {\r
255         define('_ARCHIVETYPE_DAY','day');\r
256         define('_ARCHIVETYPE_MONTH','month');\r
257 }\r
258 \r
259 \r
260 // decode path_info\r
261 if ($CONF['URLMode'] == 'pathinfo') {\r
262         $data = explode("/",serverVar('PATH_INFO'));\r
263         for ($i=0;$i<sizeof($data);$i++) {\r
264                 switch ($data[$i]) {\r
265                         case 'item':                    // item/1 (blogid)\r
266                                 $i++;\r
267                                 if ($i<sizeof($data)) $itemid = intval($data[$i]);\r
268                                 break;\r
269                         case 'archives':                // archives/1 (blogid)\r
270                                 $i++;\r
271                                 if ($i<sizeof($data)) $archivelist = intval($data[$i]);\r
272                                 break;\r
273                         case 'archive':                 // two possibilities: archive/yyyy-mm or archive/1/yyyy-mm (with blogid)\r
274                                 if ((($i+1)<sizeof($data)) && (!strstr($data[$i+1],'-')) ){\r
275                                         $blogid = intval($data[++$i]);\r
276                                 }\r
277                                 $i++;\r
278                                 if ($i<sizeof($data)) $archive = $data[$i];\r
279                                 break;\r
280                         case 'blogid':                  // blogid/1\r
281                         case 'blog':                    // blog/1\r
282                                 $i++;\r
283                                 if ($i<sizeof($data)) $blogid = intval($data[$i]);\r
284                                 break;\r
285                         case 'category':                // category/1 (catid)\r
286                         case 'catid':\r
287                                 $i++;\r
288                                 if ($i<sizeof($data)) $catid = intval($data[$i]);\r
289                                 break;\r
290                         case 'member':\r
291                                 $i++;\r
292                                 if ($i<sizeof($data)) $memberid = intval($data[$i]);\r
293                                 break;\r
294                         default:\r
295                                 // skip...\r
296                 }\r
297         }\r
298 }\r
299 \r
300 /**\r
301   * Connects to mysql server\r
302   */\r
303 function sql_connect() {\r
304         global $MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD, $MYSQL_DATABASE;\r
305 \r
306         $connection = @mysql_connect($MYSQL_HOST, $MYSQL_USER, $MYSQL_PASSWORD) or startUpError('<p>Could not connect to MySQL database.</p>','Connect Error');\r
307         mysql_select_db($MYSQL_DATABASE) or startUpError('<p>Could not select database: '. mysql_error().'</p>', 'Connect Error');\r
308 \r
309         return $connection;\r
310 }\r
311 \r
312 /**\r
313  * returns a prefixed nucleus table name\r
314  */\r
315 function sql_table($name)\r
316 {\r
317         global $MYSQL_PREFIX;\r
318 \r
319         if ($MYSQL_PREFIX)\r
320                 return $MYSQL_PREFIX . 'nucleus_' . $name;\r
321         else\r
322                 return 'nucleus_' . $name;\r
323 }\r
324 \r
325 function sendContentType($contenttype, $pagetype = '', $charset = _CHARSET) {\r
326         global $manager, $CONF;\r
327         \r
328         if (!headers_sent()) {\r
329                 // if content type is application/xhtml+xml, only send it to browsers\r
330                 // that can handle it (IE6 cannot). Otherwise, send text/html\r
331                 \r
332                 // v2.5: For admin area pages, keep sending text/html (unless it's a debug version)\r
333                 //       application/xhtml+xml still causes too much problems with the javascript implementations\r
334                 if (\r
335                                 ($contenttype == 'application/xhtml+xml')\r
336                         &&      (($CONF['UsingAdminArea'] && !$CONF['debug']) || !stristr(serverVar('HTTP_ACCEPT'),'application/xhtml+xml'))\r
337                         )\r
338                 {\r
339                         $contenttype = 'text/html';\r
340                 }\r
341                         \r
342                 $manager->notify(\r
343                         'PreSendContentType',\r
344                         array(\r
345                                 'contentType' => &$contenttype,\r
346                                 'charset' => &$charset,\r
347                                 'pageType' => $pagetype\r
348                         )\r
349                 );\r
350 \r
351                 // strip strange characters\r
352                 $contenttype = preg_replace('|[^a-z0-9-+./]|i', '', $contenttype);\r
353                 $charset = preg_replace('|[^a-z0-9-_]|i', '', $charset);\r
354                 \r
355                 header('Content-Type: ' . $contenttype . '; charset=' . $charset);                      \r
356         }\r
357         \r
358         \r
359 \r
360         \r
361 }\r
362 \r
363 /**\r
364  * Errors before the database connection has been made\r
365  */\r
366 function startUpError($msg, $title) {\r
367         ?>\r
368         <html xmlns="http://www.w3.org/1999/xhtml">\r
369                 <head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><title><?php echo htmlspecialchars($title)?></title></head>\r
370                 <body>\r
371                         <h1><?php echo htmlspecialchars($title)?></h1>\r
372                         <?php echo $msg?>\r
373                 </body>\r
374         </html>\r
375         <?php   exit;\r
376 }\r
377 \r
378 /**\r
379   * disconnects from SQL server\r
380   */\r
381 function sql_disconnect() {\r
382         @mysql_close();\r
383 }\r
384 \r
385 /**\r
386   * executes an SQL query\r
387   */\r
388 function sql_query($query) {\r
389         $res = mysql_query($query) or print("mySQL error with query $query: " . mysql_error() . '<p />');\r
390         return $res;\r
391 }\r
392 \r
393 \r
394 /**\r
395  * Highlights a specific query in a given HTML text (not within HTML tags) and returns it\r
396  *\r
397  * @param $text\r
398  *              text to be highlighted\r
399  * @param $expression\r
400  *              regular expression to be matched (can be an array of expressions as well)\r
401  * @param $highlight\r
402  *              highlight to be used (use \\0 to indicate the matched expression)\r
403  *\r
404  */\r
405 function highlight($text, $expression, $highlight) {\r
406         if (!$highlight || !$expression) return $text;\r
407         if (is_array($expression) && (count($expression) == 0))\r
408                 return $text;\r
409 \r
410         // add a tag in front (is needed for preg_match_all to work correct)\r
411         $text = '<!--h-->'.$text;\r
412 \r
413         // split the HTML up so we have HTML tags\r
414         // $matches[0][i] = HTML + text\r
415         // $matches[1][i] = HTML\r
416         // $matches[2][i] = text\r
417         preg_match_all('/(<[^>]+>)([^<>]*)/', $text, $matches);\r
418 \r
419         // throw it all together again while applying the highlight to the text pieces\r
420         $result = '';\r
421         for ($i = 0; $i < sizeof($matches[2]); $i++) {\r
422                 if ($i != 0) $result .= $matches[1][$i];\r
423 \r
424                 if (is_array($expression)) {\r
425                         foreach ($expression as $regex)\r
426                                 if ($regex)\r
427                                         $matches[2][$i] = @eregi_replace($regex,$highlight,$matches[2][$i]);\r
428                         $result .= $matches[2][$i];\r
429                 } else {\r
430                         $result .= @eregi_replace($expression,$highlight,$matches[2][$i]);\r
431                 }\r
432         }\r
433 \r
434         return $result;\r
435 }\r
436 \r
437 /**\r
438  * Parses a query into an array of expressions that can be passed on to the highlight method\r
439  */\r
440 function parseHighlight($query) {\r
441         // TODO: add more intelligent splitting logic\r
442 \r
443         // get rid of quotes\r
444         $query = preg_replace('/\'|"/','',$query);\r
445 \r
446         if (!query) return array();\r
447 \r
448         $aHighlight = explode(' ', $query);\r
449 \r
450         for ($i = 0; $i<count($aHighlight); $i++) {\r
451                 $aHighlight[$i] = trim($aHighlight[$i]);\r
452                 if (strlen($aHighlight[$i]) < 3)\r
453                         unset($aHighlight[$i]);\r
454         }\r
455 \r
456         if (count($aHighlight) == 1)\r
457                 return $aHighlight[0];\r
458         else\r
459                 return $aHighlight;\r
460 }\r
461 \r
462 /**\r
463   * Checks if email address is valid\r
464   */\r
465 function isValidMailAddress($address) {\r
466         if (preg_match('/^[a-zA-Z0-9\._-]+@[a-zA-Z0-9\._-]+\.[A-Za-z]{2,5}$/', $address))\r
467                 return 1;\r
468         else\r
469                 return 0;\r
470 }\r
471 \r
472 \r
473 // some helper functions\r
474 function getBlogIDFromName($name) {\r
475         return quickQuery('SELECT bnumber as result FROM '.sql_table('blog').' WHERE bshortname="'.addslashes($name).'"');\r
476 }\r
477 function getBlogNameFromID($id) {\r
478         return quickQuery('SELECT bname as result FROM '.sql_table('blog').' WHERE bnumber='.intval($id));\r
479 }\r
480 function getBlogIDFromItemID($itemid) {\r
481         return quickQuery('SELECT iblog as result FROM '.sql_table('item').' WHERE inumber='.intval($itemid));\r
482 }\r
483 function getBlogIDFromCommentID($commentid) {\r
484         return quickQuery('SELECT cblog as result FROM '.sql_table('comment').' WHERE cnumber='.intval($commentid));\r
485 }\r
486 function getBlogIDFromCatID($catid) {\r
487         return quickQuery('SELECT cblog as result FROM '.sql_table('category').' WHERE catid='.intval($catid));\r
488 }\r
489 function getCatIDFromName($name) {\r
490         return quickQuery('SELECT catid as result FROM '.sql_table('category').' WHERE cname="'.addslashes($name).'"');\r
491 }\r
492 function quickQuery($q) {\r
493         $res = sql_query($q);\r
494         $obj = mysql_fetch_object($res);\r
495         return $obj->result;\r
496 }\r
497 \r
498 function getPluginNameFromPid($pid) {\r
499         $obj = mysql_fetch_object(sql_query('SELECT pfile FROM '.sql_table('plugin').' WHERE pid='.intval($pid)));\r
500         return $obj->pfile;\r
501 }\r
502 \r
503 function selector() {\r
504         global $itemid, $blogid, $memberid, $query, $amount, $archivelist, $maxresults;\r
505         global $archive, $skinid, $blog, $memberinfo, $CONF, $member;\r
506         global $imagepopup, $catid;\r
507         global $manager;\r
508 \r
509         $actionNames = array('addcomment', 'sendmessage', 'createaccount', 'forgotpassword', 'votepositive', 'votenegative', 'plugin');\r
510         $action = requestVar('action');\r
511         if (in_array($action, $actionNames))\r
512         {\r
513                 global $DIR_LIBS, $errormessage;\r
514                 include_once($DIR_LIBS . 'ACTION.php');\r
515                 $a =& new ACTION();\r
516                 $errorInfo = $a->doAction($action);\r
517                 if ($errorInfo)\r
518                         $errormessage = $errorInfo['message'];\r
519         }       \r
520 \r
521         // show error when headers already sent out\r
522         if (headers_sent() && $CONF['alertOnHeadersSent']) {\r
523 \r
524                 // try to get line number/filename (extra headers_sent params only exists in PHP 4.3+)\r
525                 if (function_exists('version_compare') && version_compare('4.3.0', phpversion(), '<=')) {\r
526                         headers_sent($hsFile, $hsLine);\r
527                         $extraInfo = ' in <code>'.$hsFile.'</code> line <code>'.$hsLine.'</code>';\r
528                 } else {\r
529                         $extraInfo = '';\r
530                 }\r
531 \r
532 \r
533                 startUpError(\r
534                         '<p>The page headers have already been sent out'.$extraInfo.'. This could cause Nucleus not to work in the expected way.</p><p>Usually, this is caused by spaces or newlines at the end of the <code>config.php</code> file, at the end of the language file or at the end of a plugin file. Please check this and try again.</p><p>If you don\'t want to see this error message again, without solving the problem, set <code>$CONF[\'alertOnHeadersSent\']</code> in <code>globalfunctions.php</code> to <code>0</code></p>',\r
535                         'Page headers already sent'\r
536                 );\r
537                 exit;\r
538         }\r
539 \r
540         // make is so ?archivelist without blogname or blogid shows the archivelist\r
541         // for the default weblog\r
542         if (serverVar('QUERY_STRING') == 'archivelist')\r
543                 $archivelist = $CONF['DefaultBlog'];\r
544 \r
545         // now decide which type of skin we need\r
546         if ($itemid) {\r
547                 // itemid given -> only show that item\r
548                 $type = 'item';\r
549                 if (!$manager->existsItem($itemid,0,0))\r
550                         doError(_ERROR_NOSUCHITEM);\r
551 \r
552 \r
553                 global $itemidprev, $itemidnext, $catid, $itemtitlenext, $itemtitleprev;\r
554 \r
555                 // 1. get timestamp and blogid for item\r
556                 $query = 'SELECT itime, iblog FROM '.sql_table('item').' WHERE inumber=' . intval($itemid);\r
557                 $res = sql_query($query);\r
558                 $obj = mysql_fetch_object($res);\r
559 \r
560                 // if a different blog id has been set through the request or selectBlog(),\r
561                 // jump to correct url\r
562 //              if ($blogid && (intval($blogid) != $obj->iblog))\r
563 //                      doError(_ERROR_NOSUCHITEM);\r
564                 if ($blogid && (intval($blogid) != $obj->iblog)) {\r
565                    if (!headers_sent()) {\r
566                           $b =& $manager->getBlog($obj->iblog);\r
567                           $correctURL = $b->getURL();\r
568 \r
569                           if ($CONF['URLMode'] == 'pathinfo') {\r
570                                  if (substr($correctURL,strlen($correctURL)-1,1)=='/')\r
571                                         $correctURL .= 'item/' . $itemid;\r
572                                  else\r
573                                         $correctURL .= '/item/' . $itemid;\r
574                           }\r
575                           else\r
576                                  $correctURL .= '?itemid=' . $itemid;\r
577 \r
578                           redirect($correctURL);\r
579                           exit;\r
580                    }\r
581                    else doError(_ERROR_NOSUCHITEM);\r
582                 }\r
583 \r
584                 $blogid = $obj->iblog;\r
585                 $timestamp = strtotime($obj->itime);\r
586 \r
587                 $b =& $manager->getBlog($blogid);\r
588                 if ($b->isValidCategory($catid))\r
589                         $catextra = ' and icat=' . $catid;\r
590 \r
591                 // get previous itemid and title\r
592                 $query = 'SELECT inumber, ititle FROM '.sql_table('item').' WHERE itime<' . mysqldate($timestamp) . ' and idraft=0 and iblog=' . $blogid . $catextra . ' ORDER BY itime DESC LIMIT 1';\r
593                 $res = sql_query($query);\r
594 \r
595                 $obj = mysql_fetch_object($res);\r
596                 if ($obj) {\r
597                         $itemidprev = $obj->inumber;\r
598                         $itemtitleprev = $obj->ititle;\r
599         }\r
600 \r
601                 // get next itemid and title\r
602                 $query = 'SELECT inumber, ititle FROM '.sql_table('item').' WHERE itime>' . mysqldate($timestamp) . ' and itime <= ' . mysqldate(time()) . ' and idraft=0 and iblog=' . $blogid . $catextra . ' ORDER BY itime ASC LIMIT 1';\r
603                 $res = sql_query($query);\r
604 \r
605                 $obj = mysql_fetch_object($res);\r
606                 if ($obj) {\r
607                         $itemidnext = $obj->inumber;\r
608                         $itemtitlenext = $obj->ititle;\r
609                 }\r
610 \r
611         } elseif ($archive) {\r
612                 // show archive\r
613                 $type = 'archive';\r
614 \r
615                 // get next and prev month links\r
616                 global $archivenext, $archiveprev, $archivetype;\r
617 \r
618                 sscanf($archive,'%d-%d-%d',$y,$m,$d);\r
619                 if ($d != 0) {\r
620                         $archivetype = _ARCHIVETYPE_DAY;\r
621                         $t = mktime(0,0,0,$m,$d,$y);\r
622                         $archiveprev = strftime('%Y-%m-%d',$t - (24*60*60));\r
623                         $archivenext = strftime('%Y-%m-%d',$t + (24*60*60));\r
624 \r
625                 } else {\r
626                         $archivetype = _ARCHIVETYPE_MONTH;\r
627                         $t = mktime(0,0,0,$m,1,$y);\r
628                         $archiveprev = strftime('%Y-%m',$t - (1*24*60*60));\r
629                         $archivenext = strftime('%Y-%m',$t + (32*24*60*60));\r
630                 }\r
631 \r
632 \r
633         } elseif ($archivelist) {\r
634                 $type = 'archivelist';\r
635                 if (intval($archivelist) != 0)\r
636                         $blogid = $archivelist;\r
637                 else\r
638                         $blogid = getBlogIDFromName($archivelist);\r
639                 if (!$blogid) doError(_ERROR_NOSUCHBLOG);\r
640         } elseif ($query) {\r
641             global $startpos;\r
642                 $type = 'search';\r
643                 $query = stripslashes($query);\r
644                 if(preg_match("/^(\xA1{2}|\xe3\x80{2}|\x20)+$/",$query)){\r
645                                         $type = 'index';\r
646                 }\r
647                 $order = (_CHARSET == 'EUC-JP') ? 'EUC-JP, UTF-8,' : 'UTF-8, EUC-JP,';\r
648                 $query = mb_convert_encoding($query, _CHARSET, $order.' JIS, SJIS, ASCII');\r
649                 if (intval($blogid)==0)\r
650                         $blogid = getBlogIDFromName($blogid);\r
651                 if (!$blogid) doError(_ERROR_NOSUCHBLOG);\r
652         } elseif ($memberid) {\r
653                 $type = 'member';\r
654                 if (!MEMBER::existsID($memberid))\r
655                         doError(_ERROR_NOSUCHMEMBER);\r
656                 $memberinfo = MEMBER::createFromID($memberid);\r
657 \r
658         } elseif ($imagepopup) {\r
659                 // media object (images etc.)\r
660                 $type = 'imagepopup';\r
661 \r
662                 // TODO: check if media-object exists\r
663                 // TODO: set some vars?\r
664         } else {\r
665                 // show regular index page\r
666             global $startpos;\r
667                 $type = 'index';\r
668         }\r
669 \r
670         // decide which blog should be displayed\r
671         if (!$blogid)\r
672                 $blogid = $CONF['DefaultBlog'];\r
673 \r
674         $b =& $manager->getBlog($blogid);\r
675         $blog = $b;     // references can't be placed in global variables?\r
676         if (!$blog->isValid)\r
677                 doError(_ERROR_NOSUCHBLOG);\r
678 \r
679         // set catid if necessary\r
680         if ($catid)\r
681                 $blog->setSelectedCategory($catid);\r
682 \r
683         // decide which skin should be used\r
684         if ($skinid != '' && ($skinid == 0))\r
685                 selectSkin($skinid);\r
686         if (!$skinid)\r
687                 $skinid = $blog->getDefaultSkin();\r
688 \r
689         \r
690         $skin =& new SKIN($skinid);\r
691         if (!$skin->isValid)\r
692                 doError(_ERROR_NOSUCHSKIN);\r
693 \r
694         // parse the skin\r
695         $skin->parse($type);\r
696 }\r
697 \r
698 /**\r
699   * Show error skin with given message. An optional skin-object to use can be given\r
700   */\r
701 function doError($msg, $skin = '') {\r
702         global $errormessage, $CONF, $skinid, $blogid, $manager;\r
703 \r
704         if ($skin == '') {\r
705                 if (SKIN::existsID($skinid)) {\r
706                         $skin =& new SKIN($skinid);\r
707                 } elseif ($manager->existsBlogID($blogid)) {\r
708                         $blog =& $manager->getBlog($blogid);\r
709                         $skin =& new SKIN($blog->getDefaultSkin());\r
710                 } elseif ($CONF['DefaultBlog']) {\r
711                         $blog =& $manager->getBlog($CONF['DefaultBlog']);\r
712                         $skin =& new SKIN($blog->getDefaultSkin());\r
713                 } else {\r
714                         // this statement should actually never be executed\r
715                         $skin =& new SKIN($CONF['BaseSkin']);\r
716                 }\r
717         }\r
718 \r
719         $errormessage = $msg;\r
720         $skin->parse('error');\r
721         exit;\r
722 }\r
723 \r
724 function getConfig() {\r
725         global $CONF;\r
726 \r
727         $query = 'SELECT * FROM '.sql_table('config');\r
728         $res = sql_query($query);\r
729         while ($obj = mysql_fetch_object($res)) {\r
730                 $CONF[$obj->name] = $obj->value;\r
731         }\r
732 }\r
733 \r
734 // some checks for names of blogs, categories, templates, members, ...\r
735 function isValidShortName($name) {              return eregi('^[a-z0-9]+$', $name); }\r
736 function isValidDisplayName($name) {    return eregi('^[a-z0-9]+[a-z0-9 ]*[a-z0-9]+$', $name); }\r
737 function isValidCategoryName($name) {   return 1; } \r
738 function isValidTemplateName($name) {   return eregi('^[a-z0-9/]+$', $name); }\r
739 function isValidSkinName($name) {               return eregi('^[a-z0-9/]+$', $name); }\r
740 \r
741 // add and remove linebreaks\r
742 function addBreaks($var) {                              return nl2br($var); }\r
743 function removeBreaks($var) {                   return preg_replace("/<br \/>([\r\n])/","$1",$var); }\r
744 \r
745 // shortens a text string to maxlength ($toadd) is what needs to be added\r
746 // at the end (end length is <= $maxlength)\r
747 function shorten($text, $maxlength, $toadd) {\r
748         // 1. remove entities...\r
749         $trans = get_html_translation_table(HTML_ENTITIES);\r
750         $trans = array_flip($trans);\r
751         $text = strtr($text, $trans);\r
752         // 2. the actual shortening\r
753         if (strlen($text) > $maxlength)\r
754                 $text = mb_strimwidth($text, 0, $maxlength, $toadd, _CHARSET);\r
755         return $text;\r
756 }\r
757 \r
758 /**\r
759   * Converts a unix timestamp to a mysql DATETIME format, and places\r
760   * quotes around it.\r
761   */\r
762 function mysqldate($timestamp) {\r
763         return '"' . date('Y-m-d H:i:s',$timestamp) . '"';\r
764 }\r
765 \r
766 /**\r
767   * functions for use in index.php\r
768   */\r
769 function selectBlog($shortname) {\r
770         global $blogid, $archivelist;\r
771         $blogid = getBlogIDFromName($shortname);\r
772 \r
773         // also force archivelist variable, if it is set\r
774         if ($archivelist)\r
775                 $archivelist = $blogid;\r
776 }\r
777 \r
778 function selectSkin($skinname) {\r
779         global $skinid;\r
780         $skinid = SKIN::getIdFromName($skinname);\r
781 }\r
782 \r
783 /**\r
784  * Can take either a category ID or a category name (be aware that\r
785  * multiple categories can have the same name)\r
786  */\r
787 function selectCategory($cat) {\r
788         global $catid;\r
789         if (is_numeric($cat))\r
790                 $catid = intval($cat);\r
791         else\r
792                 $catid = getCatIDFromName($cat);\r
793 }\r
794 \r
795 function selectItem($id){\r
796         global $itemid;\r
797         $itemid = intval($id);\r
798 }\r
799 \r
800 // force the use of a language file (warning: can cause warnings)\r
801 function selectLanguage($language) {\r
802         global $DIR_LANG;\r
803         include($DIR_LANG . ereg_replace( '[\\|/]', '', $language) . '.php');\r
804 }\r
805 \r
806 function parseFile($filename) {\r
807         $handler =& new ACTIONS('fileparser');\r
808         $parser =& new PARSER(SKIN::getAllowedActionsForType('fileparser'), $handler);\r
809         $handler->parser =& $parser;\r
810 \r
811         if (!file_exists($filename)) doError('A file is missing');\r
812 \r
813         $fsize = filesize($filename);\r
814         if ($fsize <= 0)\r
815                 return;\r
816 \r
817         // read file\r
818         $fd = fopen ($filename, 'r');\r
819         $contents = fread ($fd, $fsize);\r
820         fclose ($fd);\r
821 \r
822         // parse file contents\r
823         $parser->parse($contents);\r
824 }\r
825 \r
826 /**\r
827   * Outputs a debug message\r
828   */\r
829 function debug($msg) {\r
830         echo '<p><b>' . $msg . "</b></p>\n";\r
831 }\r
832 \r
833 // shortcut\r
834 function addToLog($level, $msg) { ACTIONLOG::add($level, $msg); }\r
835 \r
836 // shows a link to help file\r
837 function help($id) {\r
838         echo helpHtml($id);\r
839 }\r
840 \r
841 function helpHtml($id) {\r
842         return helplink($id) . '<img src="documentation/icon-help.gif" width="15" height="15" alt="'._HELP_TT.'" /></a>';\r
843 }\r
844 \r
845 function helplink($id) {\r
846         return '<a href="documentation/help.html#'. $id . '" onclick="if (event &amp;&amp; event.preventDefault) event.preventDefault(); return help(this.href);">';\r
847 }\r
848 \r
849 function getMailFooter() {\r
850         $message = "\n\n-----------------------------";\r
851         $message .=  "\n   Powered by Nucleus CMS";\r
852         $message .=  "\n(http://www.nucleuscms.org/)";\r
853         return $message;\r
854 }\r
855 \r
856 /**\r
857   * Returns the name of the language to use\r
858   * preference priority: member - site\r
859   * defaults to english when no good language found\r
860   *\r
861   * checks if file exists, etc...\r
862   */\r
863 function getLanguageName() {\r
864         global $CONF, $member;\r
865 \r
866         if ($member) {\r
867                 // try to use members language\r
868                 $memlang = $member->getLanguage();\r
869 \r
870                 if (($memlang != '') && (checkLanguage($memlang)))\r
871                         return $memlang;\r
872         }\r
873 \r
874         // use default language\r
875         if (checkLanguage($CONF['Language']))\r
876                 return $CONF['Language'];\r
877         else\r
878                 return 'english';\r
879 }\r
880 \r
881 /**\r
882   * Includes a PHP file. This method can be called while parsing templates and skins\r
883   */\r
884 function includephp($filename) {\r
885         // make predefined variables global, so most simple scripts can be used here\r
886 \r
887         // apache (names taken from PHP doc)\r
888         global $GATEWAY_INTERFACE, $SERVER_NAME, $SERVER_SOFTWARE, $SERVER_PROTOCOL;\r
889         global $REQUEST_METHOD, $QUERY_STRING, $DOCUMENT_ROOT, $HTTP_ACCEPT;\r
890         global $HTTP_ACCEPT_CHARSET, $HTTP_ACCEPT_ENCODING, $HTTP_ACCEPT_LANGUAGE;\r
891         global $HTTP_CONNECTION, $HTTP_HOST, $HTTP_REFERER, $HTTP_USER_AGENT;\r
892         global $REMOTE_ADDR, $REMOTE_PORT, $SCRIPT_FILENAME, $SERVER_ADMIN;\r
893         global $SERVER_PORT, $SERVER_SIGNATURE, $PATH_TRANSLATED, $SCRIPT_NAME;\r
894         global $REQUEST_URI;\r
895 \r
896         // php (taken from PHP doc)\r
897         global $argv, $argc, $PHP_SELF, $HTTP_COOKIE_VARS, $HTTP_GET_VARS, $HTTP_POST_VARS;\r
898         global $HTTP_POST_FILES, $HTTP_ENV_VARS, $HTTP_SERVER_VARS, $HTTP_SESSION_VARS;\r
899 \r
900         // other\r
901         global $PATH_INFO, $HTTPS, $HTTP_RAW_POST_DATA, $HTTP_X_FORWARDED_FOR;\r
902 \r
903         if (@file_exists($filename)) include($filename);\r
904 }\r
905 \r
906 /**\r
907   * Checks if a certain language/plugin exists\r
908   */\r
909 function checkLanguage($lang) {\r
910         global $DIR_LANG ;\r
911         return file_exists($DIR_LANG . ereg_replace( '[\\|/]', '', $lang) . '.php');\r
912 }\r
913 function checkPlugin($plug) {\r
914         global $DIR_PLUGINS;\r
915         return file_exists($DIR_PLUGINS . ereg_replace( '[\\|/]', '', $plug) . '.php');\r
916 }\r
917 \r
918 \r
919 $CONF['ItemURL'] = $CONF['Self'];\r
920 $CONF['ArchiveURL'] = $CONF['Self'];\r
921 $CONF['ArchiveListURL'] = $CONF['Self'];\r
922 $CONF['MemberURL'] = $CONF['Self'];\r
923 $CONF['SearchURL'] = $CONF['Self'];\r
924 $CONF['BlogURL'] = $CONF['Self'];\r
925 $CONF['CategoryURL'] = $CONF['Self'];\r
926 \r
927 // switch URLMode back to normal when $CONF['Self'] ends in .php\r
928 // this avoids urls like index.php/item/13/index.php/item/15\r
929 if (    ($CONF['URLMode'] == 'pathinfo')\r
930         &&      (substr($CONF['Self'], strlen($CONF['Self']) - 4) == '.php')\r
931         ) {\r
932         $CONF['URLMode'] = 'normal';\r
933 }\r
934 \r
935 /**\r
936   * Centralisation of the functions that generate links\r
937   */\r
938 function createItemLink($itemid, $extra = '') {\r
939         global $CONF;\r
940         if ($CONF['URLMode'] == 'pathinfo')\r
941                 $link = $CONF['ItemURL'] . '/item/' . $itemid;\r
942         else\r
943                 $link = $CONF['ItemURL'] . '?itemid=' . $itemid;\r
944         return addLinkParams($link, $extra);\r
945 }\r
946 function createMemberLink($memberid, $extra = '') {\r
947         global $CONF;\r
948         if ($CONF['URLMode'] == 'pathinfo')\r
949                 $link = $CONF['MemberURL'] . '/member/' . $memberid;\r
950         else\r
951                 $link = $CONF['MemberURL'] . '?memberid=' . $memberid;\r
952         return addLinkParams($link, $extra);\r
953 }\r
954 function createCategoryLink($catid, $extra = '') {\r
955         global $CONF;\r
956         if ($CONF['URLMode'] == 'pathinfo')\r
957                 $link = $CONF['CategoryURL'] . '/category/' . $catid;\r
958         else\r
959                 $link = $CONF['CategoryURL'] . '?catid=' . $catid;\r
960         return addLinkParams($link, $extra);\r
961 }\r
962 function createArchiveListLink($blogid = '', $extra = '') {\r
963         global $CONF;\r
964         if (!$blogid)\r
965                 $blogid = $CONF['DefaultBlog'];\r
966         if ($CONF['URLMode'] == 'pathinfo')\r
967                 $link = $CONF['ArchiveListURL'] . '/archives/' . $blogid;\r
968         else\r
969                 $link = $CONF['ArchiveListURL'] . '?archivelist=' . $blogid;\r
970         return addLinkParams($link, $extra);\r
971 }\r
972 function createArchiveLink($blogid, $archive, $extra = '') {\r
973         global $CONF;\r
974         if ($CONF['URLMode'] == 'pathinfo')\r
975                 $link = $CONF['ArchiveURL'] . '/archive/'.$blogid.'/' . $archive;\r
976         else\r
977                 $link = $CONF['ArchiveURL'] . '?blogid='.$blogid.'&amp;archive=' . $archive;\r
978         return addLinkParams($link, $extra);\r
979 }\r
980 function createBlogLink($url, $params) {\r
981         return addLinkParams($url . '?', $params);\r
982 }\r
983 function createBlogidLink($blogid, $params = '') {\r
984         global $CONF;\r
985         if ($CONF['URLMode'] == 'pathinfo')\r
986                 $link = $CONF['BlogURL'] . '/blog/' . $blogid;\r
987         else\r
988                 $link = $CONF['BlogURL'] . '?blogid=' . $blogid;\r
989         return addLinkParams($link, $params);\r
990 }\r
991 \r
992 \r
993 function addLinkParams($link, $params) {\r
994         global $CONF;\r
995         if (is_array($params)) {\r
996                 if ($CONF['URLMode'] == 'pathinfo')     {\r
997                         foreach ($params as $param => $value) {\r
998                                 $link .= '/' . $param . '/' . urlencode($value);\r
999                         }\r
1000                 } else {\r
1001                         foreach ($params as $param => $value) {\r
1002                                 $link .= '&amp;' . $param . '=' . urlencode($value);\r
1003                         }\r
1004                 }\r
1005         }\r
1006         return $link;\r
1007 }\r
1008 \r
1009 /**\r
1010  * @param $querystr\r
1011  *              querystring to alter (e.g. foo=1&bar=2&x=y)\r
1012  * @param $param\r
1013  *              name of parameter to change (e.g. 'foo')\r
1014  * @param $value\r
1015  *              New value for that parameter (e.g. 3)\r
1016  * @result \r
1017  *              altered query string (for the examples above: foo=3&bar=2&x=y)\r
1018  */\r
1019 function alterQueryStr($querystr, $param, $value) {\r
1020     $vars = explode("&", $querystr);\r
1021     $set  = false;\r
1022     for ($i=0;$i<count($vars);$i++) {\r
1023         $v = explode('=',$vars[$i]);\r
1024         if ($v[0] == $param) {\r
1025             $v[1]     = $value;\r
1026             $vars[$i] = implode('=', $v);\r
1027             $set      = true;\r
1028             break;\r
1029         }\r
1030     }\r
1031     if (!$set) {$vars[] = $param . '=' . $value;}\r
1032     return ltrim(implode('&', $vars), '&');\r
1033 }\r
1034 \r
1035 // passes one variable as hidden input field (multiple fields for arrays)\r
1036 // @see passRequestVars in varsx.x.x.php\r
1037 function passVar($key, $value) {\r
1038         // array ?\r
1039         if (is_array($value)) {\r
1040                 for ($i=0;$i<sizeof($value);$i++)\r
1041                         passVar($key.'['.$i.']',$value[$i]);\r
1042                         return;\r
1043         }\r
1044 \r
1045         // other values: do stripslashes if needed\r
1046         ?><input type="hidden" name="<?php echo htmlspecialchars($key)?>" value="<?php echo htmlspecialchars(undoMagic($value))?>" /><?php\r
1047 }\r
1048 \r
1049 /*\r
1050         Date format functions (to be used from [%date(..)%] skinvars\r
1051 */\r
1052 function formatDate($format, $timestamp, $defaultFormat) {\r
1053         if ($format == 'rfc822') { \r
1054                 return date('r', $timestamp); \r
1055         } else if ($format == 'rfc822GMT') { \r
1056                 return gmdate('r', $timestamp); \r
1057         } else if ($format == 'utc') { \r
1058                 return gmdate('Y-m-d\TH:i:s\Z', $timestamp); \r
1059         } else if ($format == 'iso8601') {\r
1060         $tz = date('O', $timestamp);\r
1061         $tz = substr($tz, 0, 3) . ':' . substr($tz, 3, 2);      \r
1062                 return gmdate('Y-m-d\TH:i:s', $timestamp) . $tz;\r
1063         } else {  \r
1064                 return strftime($format ? $format : $defaultFormat,$timestamp); \r
1065         }  \r
1066 \r
1067 }\r
1068 \r
1069 function checkVars($aVars)\r
1070 {\r
1071         global $HTTP_GET_VARS, $HTTP_POST_VARS, $HTTP_COOKIE_VARS, $HTTP_ENV_VARS, $HTTP_POST_FILES, $HTTP_SESSION_VARS;\r
1072         foreach ($aVars as $varName)\r
1073         {\r
1074                 if (phpversion() >= '4.1.0')\r
1075                 {\r
1076                         if (   isset($_GET[$varName]) \r
1077                                 || isset($_POST[$varName]) \r
1078                                 || isset($_COOKIE[$varName])\r
1079                                 || isset($_ENV[$varName])\r
1080                                 || isset($_SESSION[$varName])\r
1081                                 || isset($_FILES[$varName])\r
1082                         ){\r
1083                                 die('Sorry. An error occurred.');\r
1084                         }\r
1085                 } else {\r
1086                         if (   isset($HTTP_GET_VARS[$varName]) \r
1087                                 || isset($HTTP_POST_VARS[$varName]) \r
1088                                 || isset($HTTP_COOKIE_VARS[$varName])\r
1089                                 || isset($HTTP_ENV_VARS[$varName])\r
1090                                 || isset($HTTP_SESSION_VARS[$varName])\r
1091                                 || isset($HTTP_POST_FILES[$varName])\r
1092                         ){\r
1093                                 die('Sorry. An error occurred.');\r
1094                         }               \r
1095                 }\r
1096         }\r
1097 }\r
1098 \r
1099 /** \r
1100  * Stops processing the request and redirects to the given URL.\r
1101  * - no actual contents should have been sent to the output yet\r
1102  * - the URL will be stripped of illegal or dangerous characters\r
1103  */\r
1104 function redirect($url)\r
1105 {\r
1106         $url = preg_replace('|[^a-z0-9-~+_.?#=&;,/:@%]|i', '', $url);\r
1107         header('Location: ' . $url);\r
1108         exit;\r
1109 }\r
1110 \r
1111 ?>\r