/** * @throws NotFoundException */ public static function route() { $dispatcher = Application::get('dispatcher'); $request = Application::get('request'); $response = Application::get('response'); Application::emit('route.dispatch.before'); //判断Event是否已经发送过响应 if (headers_sent()) { return; } $urlParts = parse_url($request->getRequestUri()); $route = $dispatcher->dispatch($request->getMethod(), $urlParts['path']); //[0]: Dispatch Status, [1]: handler, [2]: vars Application::emit('route.dispatch', $route); //同上 if (headers_sent()) { return; } switch ($route[0]) { case Dispatcher::NOT_FOUND: case Dispatcher::METHOD_NOT_ALLOWED: throw new NotFoundException(); break; case Dispatcher::FOUND: list(, $handler, $vars) = $route; $controller = new $handler['className']($request, $response); $ret = call_user_func([$controller, $handler['actionName']], $vars); if (headers_sent() || $ret === null) { return; } if ($ret instanceof Response && $ret !== $response) { // overwrite response $response = $ret; } else { if (is_string($ret)) { $response->setContent($ret); } if ($response->headers->get('content-type') === null) { $response->headers->set('content-type', 'text/html'); } $response->setCharset('UTF-8'); } $response->prepare($request); $response->send(); break; } }
/** * 导入 Vijos 数据库用户表 * * @param callable $progress * @param callable $done * @return bool * @throws UserException */ public function import(callable $progress = null) { if (Application::coll('User')->count() > 0) { throw new UserException('User.Importer.VijosImporter.userCollectionNotEmpty'); } $MAX_USER_ID = 1; $cursor = $this->source->selectCollection('User')->find()->sort(['_id' => 1]); foreach ($cursor as $user) { $_id = new \MongoId(); $uid = (int) $user['_id']; if ($uid > $MAX_USER_ID) { $MAX_USER_ID = $uid; } // vijos 中,username 被 escape 过 $user['user'] = htmlspecialchars_decode($user['user']); if (is_callable($progress)) { $progress($uid, $user['user']); } $doc = ['_id' => $_id, 'uid' => $uid, 'user' => $user['user'], 'luser' => UserUtil::canonicalizeUsername($user['user']), 'g' => $user['g'], 'gender' => (int) $user['sex'] + 1, 'regat' => (int) $user['treg'], 'regip' => $user['ipreg'], 'loginat' => (int) $user['tlogin'], 'loginip' => '255.255.255.255']; // 检查是否有 Email 重叠 $count = $this->source->selectCollection('User')->find(['lmail' => mb_strtolower($user['mail'], 'UTF-8')])->count(); if ($count > 1) { $doc['mail'] = Application::get('random')->generateString(20, VJ::RANDOM_CHARS) . '@openvj'; $doc['lmail'] = mb_strtolower($doc['mail'], 'UTF-8'); $doc['omail'] = $user['mail']; } else { $doc['mail'] = $user['mail']; $doc['lmail'] = UserUtil::canonicalizeEmail($doc['mail']); $doc['salt'] = $user['salt']; $doc['hash'] = 'vj2|' . base64_encode(mb_strtolower($user['user'], 'UTF-8')) . '|' . $user['pass']; } try { Application::coll('User')->insert($doc); Application::emit('user.created', [$uid]); } catch (\MongoCursorException $e) { // TODO: Duplicate user } } // Update UID counter Application::coll('System')->update(['_id' => 'UserCounter'], ['$set' => ['count' => $MAX_USER_ID + 1]], ['upsert' => true]); return true; }
/** * 创建用户 * * @param string $username * @param string $password * @param string $email * @return int UID * @throws InvalidArgumentException * @throws UserException */ public function createUser($username, $password, $email) { if (!is_string($username)) { throw new InvalidArgumentException('username', 'type_invalid'); } if (!is_string($password)) { throw new InvalidArgumentException('password', 'type_invalid'); } if (!is_string($email)) { throw new InvalidArgumentException('email', 'type_invalid'); } // 检查用户名 if (!mb_check_encoding($username, 'UTF-8')) { throw new InvalidArgumentException('username', 'encoding_invalid'); } $username = trim($username); if (!Validator::regex('/^\\S*$/')->length(3, 16)->validate($username)) { throw new InvalidArgumentException('username', 'format_invalid'); } // 检查关键字 $keyword = KeywordFilter::isContainGeneric($username); if ($keyword === false) { $keyword = KeywordFilter::isContainName($username); } if ($keyword !== false) { throw new UserException('UserManager.name_forbid', ['keyword' => $keyword]); } // 检查密码 if (!Validator::length(0, 50)->validate($password)) { throw new InvalidArgumentException('password', 'format_invalid'); } // 检查 email if (!Validator::email()->validate($email)) { throw new InvalidArgumentException('password', 'format_invalid'); } // 处理用户名 $username = VJ::removeEmoji($username); // 检查用户名和 Email 是否唯一 if (UserUtil::getUserObjectByUsername($username) !== null) { throw new UserException('UserManager.createUser.user_exists'); } if (UserUtil::getUserObjectByEmail($email) !== null) { throw new UserException('UserManager.createUser.email_exists'); } // 生成 hash & salt $hashSaltPair = $this->user_credential->password_encoder->generateHash($password); // 插入记录 try { $_id = new \MongoId(); $doc = ['_id' => $_id, 'uid' => $_id, 'user' => $username, 'luser' => UserUtil::canonicalizeUsername($username), 'mail' => $email, 'lmail' => UserUtil::canonicalizeEmail($email), 'salt' => $hashSaltPair['salt'], 'hash' => $hashSaltPair['hash'], 'g' => $email, 'gender' => VJ::USER_GENDER_UNKNOWN, 'regat' => new \MongoDate(), 'regip' => $this->request->getClientIp()]; Application::coll('User')->insert($doc); } catch (\MongoCursorException $e) { // 插入失败 throw new UserException('UserManager.createUser.user_or_email_exists'); } // 插入成功:更新 uid // 获取递增 uid $counterRec = Application::coll('System')->findAndModify(['_id' => 'UserCounter'], ['$inc' => ['count' => 1]], [], ['new' => true, 'upsert' => true]); $uid = (int) $counterRec['count']; try { // 修改 uid Application::coll('User')->update(['_id' => $_id], ['$set' => ['uid' => $uid]]); } catch (\MongoCursorException $e) { // 修改 uid 失败(uid 重复),则删除用户记录 Application::critical('createUser.uidDuplicate', ['uid' => $uid]); Application::coll('User')->remove(['_id' => $_id], ['justOne' => true]); throw new UserException('UserManager.createUser.internal'); } Application::emit('user.created', [$uid]); return $uid; }
/** * 检查 记住我 token 是否正确 * * @param string $clientToken * @param bool $secretly * @return array * @throws UserException */ public function checkRememberMeTokenCredential($token, $secretly = false) { try { $tokenRec = Application::get('token_manager')->find('rememberme', $token); if ($tokenRec === null) { throw new UserException('UserCredential.checkRememberMeTokenCredential.invalid_rememberme_token'); } //是否需要检查 user-agent 和 ip 地址呢 $user = UserUtil::getUserObjectByUid($tokenRec['data']['uid']); if (!UserUtil::isUserObjectValid($user)) { throw new UserException('UserCredential.checkRememberMeTokenCredential.user_not_valid'); } if (!$secretly) { Application::emit('user.login.succeeded', [VJ::LOGIN_TYPE_TOKEN, $user]); Application::info('credential.login.ok', ['uid' => $user['uid']]); } return $user; } catch (InvalidArgumentException $e) { throw new UserException('UserCredential.checkRememberMeTokenCredential.invalid_rememberme_token'); } }
/** * 修改主题 * * @param \MongoId $discussionId * @param string $markdown * @return array|null * @throws InvalidArgumentException * @throws UserException */ public static function modifyDiscussion(\MongoId $discussionId, $markdown) { if (!is_string($markdown)) { throw new InvalidArgumentException('markdown', 'type_invalid'); } if (!mb_check_encoding($markdown, 'UTF-8')) { throw new InvalidArgumentException('markdown', 'encoding_invalid'); } //if (!Validator::length(VJ::COMMENT_MIN, VJ::COMMENT_MAX)) { //throw new UserException('CommentUtil.content_invalid_length'); //} self::initParser(); $html = self::$parser->parse($markdown); $keyword = KeywordFilter::isContainGeneric(strip_tags($html)); if ($keyword !== false) { throw new UserException('CommentUtil.content_forbid', ['keyword' => $keyword]); } $result = Application::coll('Discussion')->update(['_id' => $discussionId], ['$set' => ['raw' => $markdown, 'html' => $html]]); if ($result['n'] === 1) { Application::emit('discussion.modify.succeeded', [$discussionId]); return ['_id' => $discussionId, 'html' => $html]; } else { return null; } }
/** * 删除回复 * * @param \MongoId $commentId * @param string $ref * @param \MongoId $replyId * @return bool * @throws InvalidArgumentException */ public static function deleteReply(\MongoId $commentId, $ref, \MongoId $replyId) { if (!is_string($ref)) { throw new InvalidArgumentException('ref', 'type_invalid'); } if (!mb_check_encoding($ref, 'UTF-8')) { throw new InvalidArgumentException('ref', 'encoding_invalid'); } $result = Application::coll('Comment')->update(['_id' => $commentId, 'ref' => $ref, 'deleted' => false, 'replies' => ['$elemMatch' => ['_id' => $replyId, 'deleted' => false]]], ['$set' => ['replies.$.deleted' => true]]); $success = $result['n'] === 1; if ($success) { Application::emit('comment.reply.delete.succeeded', [$ref, $commentId, $replyId]); } return $success; }