diff --git a/code/web/api/client/config.php.default b/code/web/api/client/config.php.default index 3c8ca869d..6cdbff401 100644 --- a/code/web/api/client/config.php.default +++ b/code/web/api/client/config.php.default @@ -17,4 +17,4 @@ */ // Url where player_auth.php is located -define('RYAPI_AUTH_SCRIPT', ''); +define('RYAPI_AUTH_SCRIPT', ''); \ No newline at end of file diff --git a/code/web/api/client/time.php b/code/web/api/client/time.php new file mode 100644 index 000000000..4b81db42a --- /dev/null +++ b/code/web/api/client/time.php @@ -0,0 +1,46 @@ +. + */ + +$tick_cache_timeout = 60; + +function ryzom_time_tick() { + $fn = RYAPI_URL.'data/cache/game_cycle.ticks'; + $handle = fopen($fn, "r"); + $version = fread($handle, 1); + $raw_tick = fread($handle, 4); + fclose($handle); + $arr = unpack("V", $raw_tick); + $tick = $arr[1]; + return sprintf("%u", $tick & 0xffffffff); +} + + +/** + * Takes a computed ryzom time array and returns a SimpleXMLElement + */ +function ryzom_time_xml($rytime) { + global $tick_cache_timeout; + $out = ryzom_time_xml_without_cache($rytime); + $filename = RYAPI_URL.'data/cache/game_cycle.ticks'; + $cache = $out->addChild('cache'); + $cache->addAttribute('created', filemtime($filename)); + $cache->addAttribute('expire', (filemtime($filename)+$tick_cache_timeout)); + return $out; +} + +?> \ No newline at end of file diff --git a/code/web/api/common/actionPage.php b/code/web/api/common/actionPage.php new file mode 100644 index 000000000..676012146 --- /dev/null +++ b/code/web/api/common/actionPage.php @@ -0,0 +1,158 @@ +. + */ + +class ryActionClass { + public $classname; + public $instance; + public $args; + public $requires; + + function __construct($classname, $instance, $args, $requires) { + $this->classname = $classname; + $this->instance = $instance; + $this->args = $args; + $this->requires = $requires; + } + +} + +class ryActionPage { + + private static $classesArgs = array(); + private static $myClasses = array(); + private static $aliases = array(); + private static $messages; + private static $haveMessage; + protected static $id; + + public $instanceName; + public $myMethods = array(); + + function __construct() { + } + + function addMethods($child_class) { + if (is_array($child_class)) { + foreach ($child_class as $c_class) + $this->myMethods = array_merge($this->myMethods, get_class_methods($c_class)); + } else { + $this->myMethods = get_class_methods($child_class); + } + } + + static function addClass($name, $classname, $args=array(), $requires=NULL) { + self::$myClasses[$name] = new ryActionClass($classname, NULL, $args, $requires); + } + + static function addAlias($aliasname, $name) { + self::$aliases[$aliasname] = $name; + } + + static function initInstance($listener) { + $i = self::$myClasses[$listener]; + if (!$i->instance) { + // requires + if ($i->requires) { + self::initInstance($i->requires); + } + if ($i->args) + $i->instance = new $i->classname($listener, $i->args); + else + $i->instance = new $i->classname($listener); + $i->instance->addMethods($i->classname); + $i->instance->instanceName = $listener; + + } + return $i->instance; + } + + static function getInstance($listener) { + return self::initInstance($listener); + } + + static function _addMSG($type='OK', $message='') { + self::$messages[] = array($type, $message); + return ''; + } + + function addMSG($type='OK', $action='', $message='') { + self::$messages[] = array($type, $message); + $this->haveMessage = $action; + return ''; + } + + static function getMSGs() { + return self::$messages; + } + + static function call($action, $url_params) { + $action_params = explode('_', $action); + + if (count($action_params) != 2) + return self::_addMSG('ERR', 'Action call error : bad params of ['.$action.']'); + + list($listener, $call) = $action_params; + if (array_key_exists($listener,self::$aliases)) + $listener = self::$aliases[$listener]; + + if (!array_key_exists($listener, self::$myClasses)) + return self::_addMSG('ERR', 'Action call error : class ['. $listener .'] not found'); + + $i = self::initInstance($listener); + + if (in_array('action'.$call, $i->myMethods)) { + $i->haveMessage = NULL; + $ret = call_user_func(array($i, 'action'.$call), $url_params); + if (!isset($_SESSION['last_action']) or $action != $_SESSION['last_action']) + $_SESSION['last_action'] = $action; + $msg = $i->haveMessage; + if ($msg and ($msg != $action)) { + $ret = self::call($msg, $url_params); + return self::_addMSG('OK', $ret); + } + return self::_addMSG('OK', $ret); + } else + return self::_addMSG('ERR', 'Action call error : action ['. $call .'] of ['. $listener .'] not found'); + } +} + +function callAction($action) { + $c = ''; + ryActionPage::call($action, ryzom_get_params()); + $msgs = ryActionPage::getMSGs(); + + foreach ($msgs as $msg) { + if ($msg[0] == 'HEADER') + $c .= $msg[1]; + } + + foreach ($msgs as $msg) { + if ($msg[0] == 'ERR') + $c .= _s('message error', $msg[1]); + else if ($msg[0] == 'MSG') + $c .= _s('message', $msg[1]); + else if ($msg[0] == 'WARNING') + $c .= _s('message warning', $msg[1]); + else if ($msg[0] != 'HEADER') + $c .= $msg[1]; + } + return $c; +} + +?> diff --git a/code/web/api/common/auth.php b/code/web/api/common/auth.php index 1d52c5d8e..83c92c70c 100644 --- a/code/web/api/common/auth.php +++ b/code/web/api/common/auth.php @@ -1,37 +1,97 @@ . + */ + + +function ryzom_app_authenticate(&$user, $ask_login=true, $welcome_message='', $webprivs=true) { $name = ryzom_get_param('name'); - $authserver = ryzom_get_param('authserver'); - $authkey = ryzom_get_param('authkey'); + $urluser = ryzom_get_param('user'); // user serialization send by auth server + $urlusercheksum = ryzom_get_param('checksum'); // user serialization checksum + $authkey = ryzom_get_param('authkey'); // InGame authkey $lang = ryzom_get_param('lang'); - $cid = ryzom_get_param('cid', ''); - $is_ingame = false; + $cid = intval(ryzom_get_param('cid')); + $is_auth_ingame = false; // we have to set the $user['lang'] even for anonymous user or we cannot display the test in the right langage if($lang == '') { - $l = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); - if($l=='fr'||$l=='en'||$l=='de'||$l=='ru'||$l=='es') - $lang = $l; - else - $lang = 'en'; + if (!isset($_SESSION['lang'])) { + $l = isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])?substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2):'en'; + if ($l=='fr'||$l=='en'||$l=='de'||$l=='ru'||$l=='es') + $lang = $l; + else + $lang = 'en'; + } else + $lang = $_SESSION['lang']; } + if ($lang!='fr'&&$lang!='en'&&$lang!='de'&&$lang!='ru'&&$lang!='es') + $lang = 'en'; + $user['message'] = ''; $user['lang'] = $lang; - $user['ig'] = false; - - if ((isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Ryzom')) || ryzom_get_param('ig')) { + $user['groups'] = array(); + + if ((isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'Ryzom')) || ryzom_get_param('ig')) $user['ig'] = true; + else + $user['ig'] = false; + + if (isset($_SESSION['user'])) { + if (ryzom_get_param('action') == 'logout') + unset($_SESSION['user']); + else { + $_SESSION['user']['ig'] = $user['ig']; + define('RYZOM_IG', $user['ig']); + $user = $_SESSION['user']; + return true; + } + } + + if ($urluser && $urlusercheksum) { + // Check $authuser (used to test app from another server ingame) + if (hash_hmac('sha1', $urluser, RYAPI_AUTH_KEY) == $urlusercheksum) { + $ig = $user['ig']; + $user = array_merge($user, unserialize(base64_decode($urluser))); + $user['ig'] = $ig; + if (!isset($user['groups'])) + $user['groups'] = array(); + define('RYZOM_IG', $user['ig']); + $_SESSION['user'] = $user; + return true; + } + } + + if ($user['ig']) { // Ingame $shardid = ryzom_get_param('shardid'); - if (!ryzom_authenticate_ingame($shardid, $cid, $name, $authkey)) - return false; - $is_ingame = true; + $error_message = ''; + if (ryzom_authenticate_ingame($shardid, $cid, $name, $authkey) || ryzom_authenticate_with_session($name, $cid, $error_message)) { + $is_auth_ingame = true; + } } else { - // Outgame : Use session + // Outgame or bad ingame auth (external server) : Use session $error_message = ''; if (!ryzom_authenticate_with_session($name, $cid, $error_message)) { + define('RYZOM_IG', false); if ($ask_login) { - $c = ''; + + if ($error_message) + $c = '

'._t($error_message).'

'; + else + $c = ''; if (!$welcome_message) $welcome_message = 'The application '._t(APP_NAME).' require authentication. Please enter your credentials'; @@ -47,18 +107,44 @@ function ryzom_app_authenticate(&$user, $ask_login=true, $welcome_message='') { } } - if ($lang) - $_SESSION['lang'] = $lang; + $_SESSION['lang'] = $lang; + define('RYZOM_IG', $user['ig']); // get user informations - $user = ryzom_user_get_info($cid); - $user['lang'] = $_SESSION['lang']; + $ig = $user['ig']; + $user = ryzom_user_get_info($cid, $webprivs, RYAPI_USE_PLAYER_STATS); + if (isset($user['creation_date'])) - $user['id'] = ryzom_get_user_id($cid, $user['char_name'], $user['creation_date']); - if ($is_ingame && $user['last_played_date'] != '0') - $user['ig'] = true; + $user['id'] = ryzom_get_user_id($cid, $user['char_name'], $user['creation_date'], $user); + + $user['gender'] = ryzom_get_user_gender($user['id']); + + $user['ig'] = $ig; + $user['lang'] = $_SESSION['lang']; + if (!isset($user['groups'])) + $user['groups'] = array(); + + if ($is_auth_ingame && $user['last_played_date'] != '0') + $user['auth_ig'] = true; else - $user['ig'] = false; + $user['auth_ig'] = false; + + if (!isset($_SESSION['translater_mode']) || ryzom_get_param('translate_this') == '0') + $_SESSION['translater_mode'] = false; + + // Set/unset translation mode + if (in_array('WTRS', $user['groups']) && ryzom_get_param('translate_this') == '1') + $_SESSION['translater_mode'] = true; + + $user['translation_mode'] = $_SESSION['translater_mode']; + +// $user['after_merge'] = $user['uid'] >= 671686; + + ryzom_unset_url_param('translate_this'); + + if (isset($user['last_played_date'])) + $_SESSION['last_played_date'] = $user['last_played_date']; + // don't send this informations to external apps unset($user['last_played_date']); unset($user['creation_date']); return true; diff --git a/code/web/api/common/bbCode.php b/code/web/api/common/bbCode.php new file mode 100644 index 000000000..b1597d5b5 --- /dev/null +++ b/code/web/api/common/bbCode.php @@ -0,0 +1,998 @@ +. + */ + +// setup bbCode formatter + +bbCode::$ig = RYZOM_IG; + +/** + * Image proxy + */ +if(!defined('IMG_PROXY')){ + $url = 'http://'.$_SERVER['HTTP_HOST'].'/app_forum/tools/imageproxy.php'; + define('IMG_PROXY', $url); +} +if (!function_exists('proxy_image_url')) { + function proxy_image_url($href, $attrs=''){ + return IMG_PROXY.'?'.($attrs != '' ? $attrs.'&' : '').'url='.urlencode($href); + } +} + + +abstract class bbCodeParser { + + /** + * @var bool + */ + private $_ig; + + /** + * @var array + */ + private $tags_ignore; + private $tags_block_open; + private $tags_block_close; + private $tags_ignore_depth; + + /** + * @var array + */ + private $open_tags; + + /** + * @var string + */ + private $last_closed_tag; + + /** + * @var int + */ + private $current_tag; + + /** + * @var array + */ + private $state; + + /** + * @param bool $ig if true, use ingame markup + */ + function __construct($ig) { + $this->_ig = $ig; + + // ignore bbcode between these tags + $this->tags_ignore = array( + 'noparse', 'code', + 'url', 'img', 'mail', 'page', 'forum', 'topic', 'post', 'wiki', 'time', 'date' + ); + + // these create block level html code, so '\n' or ' ' or '\t' around them needs to be cleared + $this->tags_block_open = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'quote', 'list', 'p'); + $this->tags_block_close = $this->tags_block_open; + if ($this->_ig) { + // ingame

is not block level when closing, so dont strip there + $key = array_search('p', $this->tags_block_close, true); + unset($this->tags_block_close[$key]); + } + + $this->state = array(); + + // reset internals + $this->reset(); + } + + /** + * Format bbcode tag + * + * @param string $tag tag name + * @param string $open open markup + * @param string $close close markup + * @param string $attr tag attributes + * @param string $text text between tags + */ + abstract function format($tag, $open, $close, $attr, $text); + + /** + * Wrapper to call Child->format(...) + * + * @param array $tag assoc array with tag info + * @return string + */ + function handle_tag($tag) { + return $this->format($tag['tag'], $tag['open'], $tag['close'], $tag['attr'], $tag['text']); + } + + /** + * Reset internals + */ + function reset() { + $this->current_tag = 0; + $this->tags_ignore_depth = 0; + + // 0'th position is used as result + $this->open_tags = array( + 0 => array('tag' => '', 'open' => '', 'close' => '', 'attr' => '', 'text' => '') + ); + + $this->last_closed_tag = false; + } + + /** + * Save working state + */ + private function _state_save() { + $this->state[] = array($this->current_tag, $this->tags_ignore_depth, $this->open_tags, $this->last_closed_tag); + $this->reset(); + } + + /** + * Restore working state + */ + private function _state_restore() { + if (!empty($this->state)) { + list($this->current_tag, $this->tags_ignore_depth, $this->open_tags, $this->last_closed_tag) = array_pop($this->state); + } + } + + /** + * Main worker. Parse $text for bbCode tags + * + * NOTE: Text must already be safe for HTML, ie. treated with htmlspecialchars() + * + * @param string $text + * @return string formatted string + */ + function bbcode($text) { + $text = str_replace("\r\n", "\n", $text); + + $split = preg_split('/(\[[a-zA-Z0-9_\/]*?(?:[= ].*?)?\])/', $text, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + foreach ($split as $chunk) { + if (substr($chunk, 0, 1) == '[' && substr($chunk, -1, 1) == ']') { + if (substr($chunk, 0, 2) == '[/') { + $this->close($chunk); + } else { + $this->open($chunk); + } + } else { + $this->text($chunk); + } + } + + return $this->result(); + } + + /** + * Push tag with args to stack + * Do not strip whitespace because tag might be invalid + * + * @param string $chunk full tag string, eg. [tag=attr] + */ + function open($chunk) { + list($tag, $attr) = $this->split_params($chunk); + + // test for [noparse] + if ($this->tags_ignore_depth > 0) { + $this->text($chunk); + } else { + $this->current_tag++; + // remember tag, attributes and complete string that was used in markup + $this->open_tags[$this->current_tag] = array('tag' => $tag, 'attr' => $attr, 'open' => $chunk, 'close' => '', 'text' => ''); + } + + if (in_array($tag, $this->tags_ignore)) { + $this->tags_ignore_depth++; + } + } + + /** + * Close tag and call tag handler to format output + * + * @param $chunk full tag string, eg. [/tag] + */ + function close($chunk) { + // extract tag name from [/name] + $tag = strtolower(substr($chunk, 2, -1)); + + if ($this->tags_ignore_depth > 0 && in_array($tag, $this->tags_ignore)) { + $this->tags_ignore_depth--; + } + + // stack underrun + if ($this->current_tag < 0) { + $this->text($chunk); + return; + } + + // ignore block + if ($this->tags_ignore_depth > 0) { + $this->text($chunk); + return; + } + + // tag mismatch + if ($this->open_tags[$this->current_tag]['tag'] !== $tag) { + // try to find first open tag for this + $key = false; + for ($i = $this->current_tag - 1; $i > 0; $i--) { + if (isset($this->open_tags[$i]['tag']) && $this->open_tags[$i]['tag'] === $tag) { + $key = $i; + break; + } + } + if ($key === false) { + $this->text($chunk); + return; + } + + // tag is open so we need to 'rewind' a bit + for ($i = $this->current_tag; $i > $key; $i--) { + $tmp_tag = $this->pop_stack(); + $this->text($tmp_tag['open'] . $tmp_tag['text']); + } + } + + // close tag + $open = $this->pop_stack(); + + // handle bbcode + $open['close'] = $chunk; + + $block_level = false; + if (in_array($tag, $this->tags_block_open)) { + $block_level = true; + // for block level element, trim whitespace from inside tag + // [tag]...text...[/tag] + $open['text'] = $this->trim_ws($open['text']); + } + $result = $this->handle_tag($open); + + // strip whitespace from text before tag 'text...[tag]' + if ($block_level) { + $ts = $this->rtrim_ws($this->open_tags[$this->current_tag]['text']); + $this->open_tags[$this->current_tag]['text'] = $ts; + } + + $this->text($result); + + $this->last_closed_tag = $open['tag']; + } + + function text($text) { + // strip whitespace after closing '[/tag]...text' + if (in_array($this->last_closed_tag, $this->tags_block_close)) { + $text = $this->ltrim_ws($text); + } + $this->open_tags[$this->current_tag]['text'] .= $text; + + $this->last_closed_tag = false; + } + + function result() { + // close tags that are still open + while ($this->current_tag > 0) { + $open = $this->pop_stack(); + + if ($this->tags_ignore_depth > 0) { + $this->tags_ignore_depth--; + // need to reparse text that's after ignore tag + $this->_state_save(); + $text = $open['open'] . $this->bbcode($open['text']); + $this->_state_restore(); + } else { + // tag was not closed proprely, include start tag with result + $text = $open['open'] . $open['text']; + } + + $this->text($text); + }; + + return $this->open_tags[0]['text']; + } + + /** + * Pop tag and text from stack and return them + * + * @return array [0] = tag, [1] = text + */ + function pop_stack() { + // remove from stack + $open = $this->open_tags[$this->current_tag]; + unset($this->open_tags[$this->current_tag]); + $this->current_tag--; + + return $open; + } + + /** + * Trim from end of string + * 'text...\s{0,}\n{1}\s{0,}' + * + * @param string $ts + * @return string + */ + function rtrim_ws($ts){ + // we want to get rid of all spaces/tabs, but only single \n, so rtrim($ts, " \t\n\r") would not work + $ts = rtrim($ts, " \t"); + if (substr($ts, -1, 1) === "\n") { + $ts = substr($ts, 0, -1); + $ts = rtrim($ts, " \t"); + } + return $ts; + } + + /** + * Trim from start of string + * '\s{0,}\n{1}...text' + * + * @param string $ts + * @return string + */ + function ltrim_ws($ts){ + // we want to get rid of all spaces/tabs, but only single \n, so ltrim($ts, " \t\n\r") would not work + $ts = ltrim($ts, " \t"); + if (substr($ts, 0, 1) === "\n") { + $ts = substr($ts, 1); + } + return $ts; + } + + /** + * Trim from both sides + * '\s{0,}\n{1}...text...\s{0,}\n{1}\s{0,} + * + * @param string $ts + * @return string + */ + function trim_ws($ts){ + $ts = $this->ltrim_ws($ts); + $ts = $this->rtrim_ws($ts); + return $ts; + } + + /** + * Extract tag parameters from [tag=params] or [tag key1=val1 key2=val2] + * + * @param type $tag + * @return type + */ + function split_params($chunk) { + if (substr($chunk, 0, 1) == '[') { + $b = '\['; + $e = '\]'; + } else { + $b = ''; + $e = ''; + } + // [1] [2] [3] + if (preg_match('/^' . $b . '([\*a-zA-Z0-9]*?)' . '(=| )' . '(.*?)' . $e . '$/', $chunk, $match)) { + $tagName = strtolower($match[1]); + if ($match[2] == '=') { + // = means single parameter + $tagParam = $match[3]; + } else { + // means multiple parameters + $tagParam = array(); + $args = preg_split('/[ ]/', $match[3], null, PREG_SPLIT_NO_EMPTY); + foreach ($args as $arg) { + $pairs = explode('=', $arg); + // preg_replace will remove possible quotes around value + if (isset($pairs[1])) { + $tagParam[strtolower($pairs[0])] = preg_replace('@("|\'|)(.*?)\\1@', '$2', $pairs[1]); + } else { + $tagParam[] = preg_replace('@("|\'|)(.*?)\\1@', '$2', $pairs[0]); + } + } + } + } else { + if (substr($chunk, 0, 1) == '[' && substr($chunk, -1, 1) == ']') { + $chunk = substr($chunk, 1, -1); + } + $tagName = strtolower($chunk); + $tagParam = ''; + } + return array($tagName, $tagParam); + } + +} + +class bbCode extends bbCodeParser { + static $legacy_sync = 1348956841; + static $legacy_shard = array( + 'ani' => 2363920179, + 'lea' => 2437578274, + 'ari' => 2358620001, + ); + + static $ig = false; + static $timezone = 'UTC'; + static $clock12h = false; + static $shardid = false; + static $lang = 'en'; + static $disabledTags = array(); + // + const COLOR_P = '#d0d0d0'; // normal text + // + const COLOR_BBCODE_TAG = '#444444'; + + static function bbDisabled($tag) { + return in_array(strtolower($tag), self::$disabledTags); + } + + static function getFontSize($value) { + $size = 16; + switch (strtolower($value)) { + case '1': case 'xx-small': $size = 9; break; + case '2': case 'x-small' : $size = 10; break; + case '3': case 'small' : $size = 13; break; + case '4': case 'medium' : $size = 16; break; + case '5': case 'large' : $size = 18; break; + case '6': case 'x-large' : $size = 24; break; + case '7': case 'xx-large': $size = 32; break; + //case '8': case 'smaller' : break; + //case '9': case 'larger' : break; + } + return $size; + } + + static function bb_noparse($code) { + return preg_replace(array('/\[/', '/\]/'), array('[', ']'), $code); + } + + static function bb_code($code) { + return '

' . self::bb_noparse($code) . '
'; + } + + static function bb_list($list) { + $result = ''; + $list = str_replace("\n[", '[', $list); + $result = ''; + return preg_replace('#