mirror of https://github.com/1099438829/apeblog
parent
e3cf01d01f
commit
172cfd9f3f
|
|
@ -5,6 +5,8 @@
|
||||||
/.env
|
/.env
|
||||||
/runtime
|
/runtime
|
||||||
/composer.lock
|
/composer.lock
|
||||||
/public/upload/image
|
/public/storage/*
|
||||||
|
/public/upload/*
|
||||||
/public/install/install.lock
|
/public/install/install.lock
|
||||||
/.constant
|
/.constant
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ namespace app\admin\controller;
|
||||||
use app\admin\extend\Util as Util;
|
use app\admin\extend\Util as Util;
|
||||||
use app\common\model\Comment as CommentModel;
|
use app\common\model\Comment as CommentModel;
|
||||||
use app\common\model\Document;
|
use app\common\model\Document;
|
||||||
use app\common\model\DocumentCategory as cModel;
|
use app\common\model\Category as cModel;
|
||||||
use think\db\exception\DataNotFoundException;
|
use think\db\exception\DataNotFoundException;
|
||||||
use think\db\exception\DbException;
|
use think\db\exception\DbException;
|
||||||
use think\db\exception\ModelNotFoundException;
|
use think\db\exception\ModelNotFoundException;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ namespace app\admin\controller;
|
||||||
|
|
||||||
use app\admin\extend\Util as Util;
|
use app\admin\extend\Util as Util;
|
||||||
use app\common\constant\Data;
|
use app\common\constant\Data;
|
||||||
use app\common\model\DocumentCategory as aModel;
|
use app\common\model\Category as aModel;
|
||||||
use app\Request;
|
use app\Request;
|
||||||
use Exception;
|
use Exception;
|
||||||
use think\db\exception\DataNotFoundException;
|
use think\db\exception\DataNotFoundException;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,7 @@ use think\db\exception\DataNotFoundException;
|
||||||
use think\db\exception\DbException;
|
use think\db\exception\DbException;
|
||||||
use think\db\exception\ModelNotFoundException;
|
use think\db\exception\ModelNotFoundException;
|
||||||
use think\exception\ValidateException;
|
use think\exception\ValidateException;
|
||||||
use think\facade\Config;
|
use think\facade\Filesystem;
|
||||||
use think\File as Filesystem;
|
|
||||||
use think\Request;
|
use think\Request;
|
||||||
|
|
||||||
class File extends AuthController
|
class File extends AuthController
|
||||||
|
|
@ -28,37 +27,18 @@ class File extends AuthController
|
||||||
$storage_type = system_config("storage_type") ?: 1;//默认未本地存储
|
$storage_type = system_config("storage_type") ?: 1;//默认未本地存储
|
||||||
switch ($storage_type) {
|
switch ($storage_type) {
|
||||||
case 1:
|
case 1:
|
||||||
// 获取文件基本信息
|
|
||||||
$fileInfo = pathinfo($file);
|
|
||||||
// 获取文件后缀
|
|
||||||
$extension = strtolower($file->getOriginalExtension());
|
|
||||||
// 获取文件地址和名称
|
|
||||||
$filePath = $fileInfo['dirname'] . '/' . $fileInfo['basename'];
|
|
||||||
// 文件地址转文件类
|
|
||||||
$file = new Filesystem($filePath);
|
|
||||||
// 文件转存目录(按自己喜好定义就行)
|
|
||||||
$savePath = Config::get('filesystem.disks.public.root').DIRECTORY_SEPARATOR.$fileType.DIRECTORY_SEPARATOR.date('Y-m-d').DIRECTORY_SEPARATOR;
|
|
||||||
// 新的文件名(按自己喜好生成就行)
|
|
||||||
$fileName = $file->md5() . '.' . $extension;
|
|
||||||
//获取文件类型
|
//获取文件类型
|
||||||
$fileMime = $file->getMime();
|
$fileMime = $file->getMime();
|
||||||
//获取文件尺寸
|
//获取文件尺寸
|
||||||
$fileSize = $file->getSize();
|
$fileSize = $file->getSize();
|
||||||
// 转移临时文件到指定目录
|
$fileName = Filesystem::disk('public')->putFile($fileType, $file);
|
||||||
$file->move( $savePath,$fileName);
|
$filePath = Filesystem::disk('public')->url($fileName);
|
||||||
//获取新文件地址
|
|
||||||
$filePath = Config::get('filesystem.disks.public.url').DIRECTORY_SEPARATOR.$fileType.DIRECTORY_SEPARATOR.date('Y-m-d').DIRECTORY_SEPARATOR.$fileName;
|
|
||||||
//转换因为win导致的兼容问题
|
//转换因为win导致的兼容问题
|
||||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||||
$filePath = str_replace(DIRECTORY_SEPARATOR, '/', $filePath);
|
$filePath = str_replace(DIRECTORY_SEPARATOR, '/', $filePath);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 2:
|
//其余云oss 自行开发
|
||||||
// $saveName = Filesystem::putFile('image', $file);
|
|
||||||
// $ext = $file->getOriginalExtension();
|
|
||||||
// $key = '/image/' . date('Ymd') . "/" . substr(md5($file->getRealPath()), 0, 5) . date('YmdHis') . rand(0, 9999) . '.' . $ext;
|
|
||||||
// $filePath = QcloudCoService::put($key, $file->getRealPath());
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
return Attachment::addAttachment($this->request->param("cid", 0), $fileName, $filePath, 'image', $fileMime, $fileSize, $storage_type) ? app("json")->code()->success("上传成功", ['filePath' => file_cdn($filePath), "name" => $fileName]) : app("json")->fail("上传失败");
|
return Attachment::addAttachment($this->request->param("cid", 0), $fileName, $filePath, 'image', $fileMime, $fileSize, $storage_type) ? app("json")->code()->success("上传成功", ['filePath' => file_cdn($filePath), "name" => $fileName]) : app("json")->fail("上传失败");
|
||||||
}
|
}
|
||||||
|
|
@ -134,26 +114,12 @@ class File extends AuthController
|
||||||
'fileExt' => $fileExt
|
'fileExt' => $fileExt
|
||||||
// 更多规则请看“上传验证”的规则,文档地址https://www.kancloud.cn/manual/thinkphp6_0/1037629#_444
|
// 更多规则请看“上传验证”的规则,文档地址https://www.kancloud.cn/manual/thinkphp6_0/1037629#_444
|
||||||
]])->check(['file' => $file]);
|
]])->check(['file' => $file]);
|
||||||
// 获取文件基本信息
|
|
||||||
$fileInfo = pathinfo($file);
|
|
||||||
// 获取文件后缀
|
|
||||||
$extension = strtolower($file->getOriginalExtension());
|
|
||||||
// 获取文件地址和名称
|
|
||||||
$filePath = $fileInfo['dirname'] . '/' . $fileInfo['basename'];
|
|
||||||
// 文件地址转文件类
|
|
||||||
$file = new Filesystem($filePath);
|
|
||||||
// 文件转存目录(按自己喜好定义就行)
|
|
||||||
$savePath = Config::get('filesystem.disks.public.root').DIRECTORY_SEPARATOR.$fileType.DIRECTORY_SEPARATOR.date('Y-m-d').DIRECTORY_SEPARATOR;
|
|
||||||
// 新的文件名(按自己喜好生成就行)
|
|
||||||
$fileName = $file->md5() . '.' . $extension;
|
|
||||||
//获取文件类型
|
//获取文件类型
|
||||||
$fileMime = $file->getMime();
|
$fileMime = $file->getMime();
|
||||||
//获取文件尺寸
|
//获取文件尺寸
|
||||||
$fileSize = $file->getSize();
|
$fileSize = $file->getSize();
|
||||||
// 转移临时文件到指定目录
|
$fileName = Filesystem::disk('public')->putFile($fileType, $file);
|
||||||
$file->move( $savePath,$fileName);
|
$filePath = Filesystem::disk('public')->url($fileName);
|
||||||
//获取新文件地址
|
|
||||||
$filePath = Config::get('filesystem.disks.public.url').DIRECTORY_SEPARATOR.$fileType.DIRECTORY_SEPARATOR.date('Y-m-d').DIRECTORY_SEPARATOR.$fileName;
|
|
||||||
//转换因为win导致的兼容问题
|
//转换因为win导致的兼容问题
|
||||||
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
|
||||||
$filePath = str_replace(DIRECTORY_SEPARATOR, '/', $filePath);
|
$filePath = str_replace(DIRECTORY_SEPARATOR, '/', $filePath);
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ class Image extends AuthController
|
||||||
*/
|
*/
|
||||||
public function category()
|
public function category()
|
||||||
{
|
{
|
||||||
return app("json")->success(AttachmentCategory::buildNodes("image", 0, $this->request->param("title", "")));
|
return app("json")->success(AttachmentCategory::buildNodes($this->type, 0, $this->request->param("title", "")));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,11 @@ namespace app\admin\controller;
|
||||||
use app\common\model\AdminAuth;
|
use app\common\model\AdminAuth;
|
||||||
use app\common\model\Document;
|
use app\common\model\Document;
|
||||||
use app\common\model\Document as DocumentModel;
|
use app\common\model\Document as DocumentModel;
|
||||||
use app\common\model\DocumentCategory;
|
use app\common\model\Category;
|
||||||
use app\common\model\DocumentCategory as DocumentCategoryModel;
|
use app\common\model\Category as DocumentCategoryModel;
|
||||||
use app\common\model\FriendLink;
|
use app\common\model\FriendLink;
|
||||||
use app\common\model\MessageForm;
|
use app\common\model\MessageForm;
|
||||||
|
use app\common\model\AdminNotify;
|
||||||
use app\common\model\User;
|
use app\common\model\User;
|
||||||
use Exception;
|
use Exception;
|
||||||
use think\db\exception\DataNotFoundException;
|
use think\db\exception\DataNotFoundException;
|
||||||
|
|
@ -31,7 +32,7 @@ class Index extends AuthController
|
||||||
cache(AdminAuth::getMenuCacheKey($this->adminId), $menuList, 1 * 60 * 60);
|
cache(AdminAuth::getMenuCacheKey($this->adminId), $menuList, 1 * 60 * 60);
|
||||||
}
|
}
|
||||||
$this->assign("menu", $menuList);
|
$this->assign("menu", $menuList);
|
||||||
$messageList = ['data' => [], 'count' => 0];
|
$messageList = AdminNotify::pageList(5);
|
||||||
$this->assign("message", $messageList);
|
$this->assign("message", $messageList);
|
||||||
return $this->fetch();
|
return $this->fetch();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ use think\db\exception\ModelNotFoundException;
|
||||||
* @author 木子的忧伤
|
* @author 木子的忧伤
|
||||||
* @date 2021-02-15 23:22
|
* @date 2021-02-15 23:22
|
||||||
*/
|
*/
|
||||||
class DocumentCategory extends BaseModel
|
class Category extends BaseModel
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* 列表
|
* 列表
|
||||||
|
|
@ -40,7 +40,7 @@ class Document extends BaseModel
|
||||||
$model = $model->order("sort desc")->order("id desc");
|
$model = $model->order("sort desc")->order("id desc");
|
||||||
$count = self::counts($model);
|
$count = self::counts($model);
|
||||||
if ($where['page'] && $where['limit']) $model = $model->page((int)$where['page'], (int)$where['limit']);
|
if ($where['page'] && $where['limit']) $model = $model->page((int)$where['page'], (int)$where['limit']);
|
||||||
$categoryList = DocumentCategory::column('title', 'id');
|
$categoryList = Category::column('title', 'id');
|
||||||
$data = $model->select()->each(function ($item) use ($categoryList) {
|
$data = $model->select()->each(function ($item) use ($categoryList) {
|
||||||
$item->category_title = $categoryList[$item->category_id] ?? "";
|
$item->category_title = $categoryList[$item->category_id] ?? "";
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use think\db\exception\DataNotFoundException;
|
||||||
use think\db\exception\DbException;
|
use think\db\exception\DbException;
|
||||||
use think\db\exception\ModelNotFoundException;
|
use think\db\exception\ModelNotFoundException;
|
||||||
use think\facade\Session;
|
use think\facade\Session;
|
||||||
|
use mailer\Mailer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户管理
|
* 用户管理
|
||||||
|
|
@ -69,8 +70,16 @@ class User extends BaseModel
|
||||||
return self::setErrorInfo("账号注册失败,请稍后再试");
|
return self::setErrorInfo("账号注册失败,请稍后再试");
|
||||||
} else {
|
} else {
|
||||||
//开始发送激活邮件,激活邮件处理
|
//开始发送激活邮件,激活邮件处理
|
||||||
|
//TODO 这里暂时未完成 设计是 支持模板来配置这里的任务 后台可以编辑模板这样子
|
||||||
|
$mailer = new Mailer();
|
||||||
|
$mailer->from('10086@qq.com')
|
||||||
|
->to('10086@qq.com')
|
||||||
|
->subject('邮件主题')
|
||||||
|
->text('邮件内容')
|
||||||
|
->send();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use app\common\constant\Data;
|
use app\common\constant\Data;
|
||||||
use app\common\model\Comment;
|
use app\common\model\Comment;
|
||||||
use app\common\model\Document;
|
use app\common\model\Document;
|
||||||
use app\common\model\DocumentCategory;
|
use app\common\model\Category;
|
||||||
use app\common\model\Advert;
|
use app\common\model\Advert;
|
||||||
use app\common\model\FriendLink;
|
use app\common\model\FriendLink;
|
||||||
use app\common\model\Tag;
|
use app\common\model\Tag;
|
||||||
|
|
@ -44,7 +44,7 @@ function get_document_category_list()
|
||||||
//缓存文章菜单
|
//缓存文章菜单
|
||||||
$documentCategory = cache(Data::DATA_DOCUMENT_CATEGORY_LIST);
|
$documentCategory = cache(Data::DATA_DOCUMENT_CATEGORY_LIST);
|
||||||
if ($documentCategory === null) {
|
if ($documentCategory === null) {
|
||||||
$documentCategoryList = DocumentCategory::where('status', 1)->order('sort asc')->select()->toArray();
|
$documentCategoryList = Category::where('status', 1)->order('sort asc')->select()->toArray();
|
||||||
//转换,让id作为数组的键
|
//转换,让id作为数组的键
|
||||||
$documentCategory = [];
|
$documentCategory = [];
|
||||||
foreach ($documentCategoryList as $item) {
|
foreach ($documentCategoryList as $item) {
|
||||||
|
|
@ -112,7 +112,7 @@ function get_document_category_by_name($name, $field = false)
|
||||||
* @param int $row
|
* @param int $row
|
||||||
* @param string $where
|
* @param string $where
|
||||||
* @param string $orderby
|
* @param string $orderby
|
||||||
* @return DocumentCategory[]|array|bool|Collection
|
* @return Category[]|array|bool|Collection
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @throws DataNotFoundException
|
* @throws DataNotFoundException
|
||||||
* @throws DbException
|
* @throws DbException
|
||||||
|
|
@ -158,7 +158,7 @@ function tpl_get_channel($type, $typeId, $row = 100, $where = '', $orderby = '')
|
||||||
if (!$dc) {
|
if (!$dc) {
|
||||||
throw new Exception('分类不存在或已删除!');
|
throw new Exception('分类不存在或已删除!');
|
||||||
}
|
}
|
||||||
$tempArr = DocumentCategory::where('id', 'in', $dc['child'])->where('status', 1)->limit($row);
|
$tempArr = Category::where('id', 'in', $dc['child'])->where('status', 1)->limit($row);
|
||||||
$tempArr = $tempArr->select();
|
$tempArr = $tempArr->select();
|
||||||
foreach ($tempArr as $key => $item) {
|
foreach ($tempArr as $key => $item) {
|
||||||
//根据栏目类型,生成栏目url
|
//根据栏目类型,生成栏目url
|
||||||
|
|
@ -185,7 +185,7 @@ function tpl_get_channel($type, $typeId, $row = 100, $where = '', $orderby = '')
|
||||||
$dc = get_document_category($typeId);
|
$dc = get_document_category($typeId);
|
||||||
if ($dc['pid'] != 0) {
|
if ($dc['pid'] != 0) {
|
||||||
//获取根分类,此操作读取数据库,非缓存!
|
//获取根分类,此操作读取数据库,非缓存!
|
||||||
$dc = DocumentCategory::where('pid', 0)->where('status', 1)
|
$dc = Category::where('pid', 0)->where('status', 1)
|
||||||
->where("CONCAT(',',child,',') like '%,$typeId,%'")->limit($row);
|
->where("CONCAT(',',child,',') like '%,$typeId,%'")->limit($row);
|
||||||
$dc = $dc->find();
|
$dc = $dc->find();
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +197,7 @@ function tpl_get_channel($type, $typeId, $row = 100, $where = '', $orderby = '')
|
||||||
break;
|
break;
|
||||||
case 'where':
|
case 'where':
|
||||||
//根据自定义条件获取分类(where语句),此操作读取数据库,非缓存!
|
//根据自定义条件获取分类(where语句),此操作读取数据库,非缓存!
|
||||||
$tempArr = DocumentCategory::where('status', 1)->where($where)->order($orderby)->limit($row);
|
$tempArr = Category::where('status', 1)->where($where)->order($orderby)->limit($row);
|
||||||
$tempArr = $tempArr->select();
|
$tempArr = $tempArr->select();
|
||||||
foreach ($tempArr as $key => $item) {
|
foreach ($tempArr as $key => $item) {
|
||||||
//根据栏目类型,生成栏目url
|
//根据栏目类型,生成栏目url
|
||||||
|
|
@ -208,7 +208,7 @@ function tpl_get_channel($type, $typeId, $row = 100, $where = '', $orderby = '')
|
||||||
break;
|
break;
|
||||||
case 'ids':
|
case 'ids':
|
||||||
//根据多个栏目id,逗号隔开的那种,获得栏目列表
|
//根据多个栏目id,逗号隔开的那种,获得栏目列表
|
||||||
$tempArr = DocumentCategory::where('status', 1)->where('id', 'in', $typeId)->order($orderby)->limit($row);
|
$tempArr = Category::where('status', 1)->where('id', 'in', $typeId)->order($orderby)->limit($row);
|
||||||
$tempArr = $tempArr->select();
|
$tempArr = $tempArr->select();
|
||||||
foreach ($tempArr as $key => $item) {
|
foreach ($tempArr as $key => $item) {
|
||||||
//根据栏目类型,生成栏目url
|
//根据栏目类型,生成栏目url
|
||||||
|
|
@ -1038,7 +1038,7 @@ function comment_face($incoming_comment,$path)
|
||||||
* @param int $row
|
* @param int $row
|
||||||
* @param string $where
|
* @param string $where
|
||||||
* @param string $orderby
|
* @param string $orderby
|
||||||
* @return DocumentCategory[]|array|bool|Collection
|
* @return Category[]|array|bool|Collection
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
* @throws DataNotFoundException
|
* @throws DataNotFoundException
|
||||||
* @throws DbException
|
* @throws DbException
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use app\admin\extend\Util as Util;
|
||||||
use app\common\constant\Data;
|
use app\common\constant\Data;
|
||||||
use app\common\model\Comment as commentModel;
|
use app\common\model\Comment as commentModel;
|
||||||
use app\common\model\Document;
|
use app\common\model\Document;
|
||||||
use app\common\model\DocumentCategory;
|
use app\common\model\Category;
|
||||||
use app\Request;
|
use app\Request;
|
||||||
use think\db\exception\DataNotFoundException;
|
use think\db\exception\DataNotFoundException;
|
||||||
use think\db\exception\DbException;
|
use think\db\exception\DbException;
|
||||||
|
|
@ -50,7 +50,7 @@ class Article extends Base
|
||||||
}
|
}
|
||||||
//赋值分类id,可能是通过栏目分类id获取的栏目分类数据
|
//赋值分类id,可能是通过栏目分类id获取的栏目分类数据
|
||||||
$id = $dc['id'];
|
$id = $dc['id'];
|
||||||
$documentCategoryModel = new DocumentCategory();
|
$documentCategoryModel = new Category();
|
||||||
//栏目存在 增加访问量
|
//栏目存在 增加访问量
|
||||||
$documentCategoryModel->where('id|alias', $id)->inc('view')->update();
|
$documentCategoryModel->where('id|alias', $id)->inc('view')->update();
|
||||||
//判断后台统计配置是否开启 1 开启
|
//判断后台统计配置是否开启 1 开启
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\index\controller;
|
||||||
|
use app\BaseController;
|
||||||
|
|
||||||
|
class Oauth extends BaseController
|
||||||
|
{
|
||||||
|
|
||||||
|
//登录地址
|
||||||
|
public function login($type = null)
|
||||||
|
{
|
||||||
|
if ($type == null) {
|
||||||
|
$this->error('参数错误');
|
||||||
|
}
|
||||||
|
// 获取对象实例
|
||||||
|
$sns = \liliuwei\social\Oauth::getInstance($type);
|
||||||
|
//跳转到授权页面
|
||||||
|
$this->redirect($sns->getRequestCodeURL());
|
||||||
|
|
||||||
|
//前端登录demo
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'qq'])}">QQ登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'sina'])}">新浪微博登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'weixin'])}">微信登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'baidu'])}">百度登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'gitee'])}">gitee登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'github'])}">github登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'oschaina'])}">oschaina登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'google'])}">google登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'facebook'])}">facebook登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'taobao'])}">淘宝登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'douyin'])}">抖音登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'xiaomi'])}">小米登录</a>
|
||||||
|
// <a href="{:url('Oauth/login',['type'=>'dingtalk'])}">钉钉登录</a>
|
||||||
|
}
|
||||||
|
|
||||||
|
//授权回调地址
|
||||||
|
public function callback($type = null, $code = null)
|
||||||
|
{
|
||||||
|
if ($type == null || $code == null) {
|
||||||
|
$this->error('参数错误');
|
||||||
|
}
|
||||||
|
$sns = \liliuwei\social\Oauth::getInstance($type);
|
||||||
|
// 获取TOKEN
|
||||||
|
$token = $sns->getAccessToken($code);
|
||||||
|
//获取当前第三方登录用户信息
|
||||||
|
if (is_array($token)) {
|
||||||
|
$user_info = \liliuwei\social\GetInfo::getInstance($type, $token);
|
||||||
|
dump($user_info);// 获取第三方用户资料
|
||||||
|
$sns->openid();//统一使用$sns->openid()获取openid
|
||||||
|
//$sns->unionid();//QQ和微信、淘宝可以获取unionid
|
||||||
|
dump($sns->openid());
|
||||||
|
//TODO
|
||||||
|
echo '登录成功!!';
|
||||||
|
echo '正在持续开发中,敬请期待!!';
|
||||||
|
} else {
|
||||||
|
echo "获取第三方用户的基本信息失败";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -25,7 +25,11 @@
|
||||||
"phpmailer/phpmailer": "^6.5",
|
"phpmailer/phpmailer": "^6.5",
|
||||||
"spatie/macroable": "^1.0",
|
"spatie/macroable": "^1.0",
|
||||||
"topthink/think-view": "^1.0",
|
"topthink/think-view": "^1.0",
|
||||||
"ext-iconv": "*"
|
"ext-iconv": "*",
|
||||||
|
"liliuwei/php-sitemap": "^1.0",
|
||||||
|
"liliuwei/thinkphp-social": "^1.3",
|
||||||
|
"topthink/think-filesystem": "^2.0",
|
||||||
|
"yzh52521/think-mail": "^3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"symfony/var-dumper": "^4.2"
|
"symfony/var-dumper": "^4.2"
|
||||||
|
|
@ -39,7 +43,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
"preferred-install": "dist"
|
"preferred-install": "dist",
|
||||||
|
"allow-plugins": {
|
||||||
|
"topthink/think-installer": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"post-autoload-dump": [
|
"post-autoload-dump": [
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置文件
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'scheme' => 'smtp',
|
||||||
|
'host' => '', // 服务器地址
|
||||||
|
'username' => '',
|
||||||
|
'password' => '', // 密码
|
||||||
|
'port' => 465, // SMTP服务器端口号,一般为25
|
||||||
|
'options' => [],
|
||||||
|
'dsn' => '',
|
||||||
|
'debug' => false, // 开启debug模式会直接抛出异常, 记录邮件发送日志
|
||||||
|
'left_delimiter' => '{', // 模板变量替换左定界符, 可选, 默认为 {
|
||||||
|
'right_delimiter' => '}', // 模板变量替换右定界符, 可选, 默认为 }
|
||||||
|
'log_drive' => '', // 日志驱动类, 可选, 如果启用必须实现静态 public static function write($content, $level = 'debug') 方法
|
||||||
|
'log_path' => '', // 日志路径, 可选, 不配置日志驱动时启用默认日志驱动, 默认路径是 /path/to/tp-mailer/log, 要保证该目录有可写权限, 最好配置自己的日志路径
|
||||||
|
'embed' => 'cid:', // 邮件中嵌入图片元数据标记
|
||||||
|
];
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | Copyright (c) 2006-2016 http://thinkphp.cn All rights reserved.
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
// | Author: yunwuxin <448901948@qq.com>
|
||||||
|
// +----------------------------------------------------------------------
|
||||||
|
|
||||||
|
return [
|
||||||
|
'channels' => [
|
||||||
|
'qq' => [
|
||||||
|
'client_id' => '',
|
||||||
|
'client_secret' => '',
|
||||||
|
],
|
||||||
|
'weibo' => [
|
||||||
|
'client_id' => '',
|
||||||
|
'client_secret' => '',
|
||||||
|
],
|
||||||
|
'github' => [
|
||||||
|
'client_id' => '',
|
||||||
|
'client_secret' => '',
|
||||||
|
],
|
||||||
|
'wechat' => [
|
||||||
|
'client_id' => '',
|
||||||
|
'client_secret' => '',
|
||||||
|
],
|
||||||
|
'topthink' => [
|
||||||
|
'client_id' => '',
|
||||||
|
'client_secret' => '',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'route' => false,
|
||||||
|
'controller' => \yunwuxin\social\Controller::class,
|
||||||
|
];
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 174 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 181 KiB |
|
|
@ -8,6 +8,7 @@ $baseDir = dirname($vendorDir);
|
||||||
return array(
|
return array(
|
||||||
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
|
'Attribute' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
|
||||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||||
|
'Normalizer' => $vendorDir . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
|
||||||
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||||
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
|
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
|
||||||
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,14 @@ $vendorDir = dirname(__DIR__);
|
||||||
$baseDir = dirname($vendorDir);
|
$baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
|
|
||||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
|
||||||
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
|
||||||
'35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php',
|
|
||||||
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
|
||||||
|
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
|
||||||
|
'9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
|
||||||
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
|
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
|
||||||
|
'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||||
|
'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||||
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
|
'35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php',
|
||||||
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
|
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||||
'1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php',
|
'1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php',
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,38 @@ $baseDir = dirname($vendorDir);
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
'think\\view\\driver\\' => array($vendorDir . '/topthink/think-view/src'),
|
'think\\view\\driver\\' => array($vendorDir . '/topthink/think-view/src'),
|
||||||
|
'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'),
|
||||||
'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'),
|
'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'),
|
||||||
'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'),
|
'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'),
|
||||||
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src'),
|
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-filesystem/src', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src'),
|
||||||
|
'mailer\\' => array($vendorDir . '/yzh52521/think-mail/src/mailer'),
|
||||||
|
'liliuwei\\social\\' => array($vendorDir . '/liliuwei/thinkphp-social/src'),
|
||||||
|
'liliuwei\\sitemap\\' => array($vendorDir . '/liliuwei/php-sitemap/src'),
|
||||||
'app\\' => array($baseDir . '/app'),
|
'app\\' => array($baseDir . '/app'),
|
||||||
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
|
||||||
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
|
'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'),
|
||||||
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
|
||||||
|
'Symfony\\Polyfill\\Intl\\Normalizer\\' => array($vendorDir . '/symfony/polyfill-intl-normalizer'),
|
||||||
|
'Symfony\\Polyfill\\Intl\\Idn\\' => array($vendorDir . '/symfony/polyfill-intl-idn'),
|
||||||
|
'Symfony\\Contracts\\Service\\' => array($vendorDir . '/symfony/service-contracts'),
|
||||||
|
'Symfony\\Contracts\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher-contracts'),
|
||||||
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
|
'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'),
|
||||||
|
'Symfony\\Component\\Mime\\' => array($vendorDir . '/symfony/mime'),
|
||||||
|
'Symfony\\Component\\Mailer\\' => array($vendorDir . '/symfony/mailer'),
|
||||||
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
|
'Symfony\\Component\\HttpFoundation\\' => array($vendorDir . '/symfony/http-foundation'),
|
||||||
|
'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'),
|
||||||
'Spatie\\Macroable\\' => array($vendorDir . '/spatie/macroable/src'),
|
'Spatie\\Macroable\\' => array($vendorDir . '/spatie/macroable/src'),
|
||||||
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
|
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
|
||||||
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
|
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
|
||||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
|
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'),
|
||||||
|
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
|
||||||
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
|
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
|
||||||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
||||||
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
|
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
|
||||||
|
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
|
||||||
|
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
|
||||||
'FormBuilder\\' => array($vendorDir . '/xaboy/form-builder/src'),
|
'FormBuilder\\' => array($vendorDir . '/xaboy/form-builder/src'),
|
||||||
|
'Egulias\\EmailValidator\\' => array($vendorDir . '/egulias/email-validator/src'),
|
||||||
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
|
'Doctrine\\Common\\Lexer\\' => array($vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer'),
|
||||||
'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'),
|
'Doctrine\\Common\\Annotations\\' => array($vendorDir . '/doctrine/annotations/lib/Doctrine/Common/Annotations'),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@ namespace Composer\Autoload;
|
||||||
class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
{
|
{
|
||||||
public static $files = array (
|
public static $files = array (
|
||||||
'9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
|
|
||||||
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
|
||||||
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
|
||||||
'35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php',
|
|
||||||
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
|
||||||
|
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
|
||||||
|
'9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
|
||||||
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
|
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
|
||||||
|
'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php',
|
||||||
|
'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php',
|
||||||
|
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
|
||||||
|
'35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php',
|
||||||
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
|
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
|
||||||
'1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
|
'1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php',
|
||||||
);
|
);
|
||||||
|
|
@ -21,10 +23,20 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
't' =>
|
't' =>
|
||||||
array (
|
array (
|
||||||
'think\\view\\driver\\' => 18,
|
'think\\view\\driver\\' => 18,
|
||||||
|
'think\\composer\\' => 15,
|
||||||
'think\\captcha\\' => 14,
|
'think\\captcha\\' => 14,
|
||||||
'think\\app\\' => 10,
|
'think\\app\\' => 10,
|
||||||
'think\\' => 6,
|
'think\\' => 6,
|
||||||
),
|
),
|
||||||
|
'm' =>
|
||||||
|
array (
|
||||||
|
'mailer\\' => 7,
|
||||||
|
),
|
||||||
|
'l' =>
|
||||||
|
array (
|
||||||
|
'liliuwei\\social\\' => 16,
|
||||||
|
'liliuwei\\sitemap\\' => 17,
|
||||||
|
),
|
||||||
'a' =>
|
'a' =>
|
||||||
array (
|
array (
|
||||||
'app\\' => 4,
|
'app\\' => 4,
|
||||||
|
|
@ -34,8 +46,15 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
'Symfony\\Polyfill\\Php80\\' => 23,
|
'Symfony\\Polyfill\\Php80\\' => 23,
|
||||||
'Symfony\\Polyfill\\Php72\\' => 23,
|
'Symfony\\Polyfill\\Php72\\' => 23,
|
||||||
'Symfony\\Polyfill\\Mbstring\\' => 26,
|
'Symfony\\Polyfill\\Mbstring\\' => 26,
|
||||||
|
'Symfony\\Polyfill\\Intl\\Normalizer\\' => 33,
|
||||||
|
'Symfony\\Polyfill\\Intl\\Idn\\' => 26,
|
||||||
|
'Symfony\\Contracts\\Service\\' => 26,
|
||||||
|
'Symfony\\Contracts\\EventDispatcher\\' => 34,
|
||||||
'Symfony\\Component\\VarDumper\\' => 28,
|
'Symfony\\Component\\VarDumper\\' => 28,
|
||||||
|
'Symfony\\Component\\Mime\\' => 23,
|
||||||
|
'Symfony\\Component\\Mailer\\' => 25,
|
||||||
'Symfony\\Component\\HttpFoundation\\' => 33,
|
'Symfony\\Component\\HttpFoundation\\' => 33,
|
||||||
|
'Symfony\\Component\\EventDispatcher\\' => 34,
|
||||||
'Spatie\\Macroable\\' => 17,
|
'Spatie\\Macroable\\' => 17,
|
||||||
),
|
),
|
||||||
'P' =>
|
'P' =>
|
||||||
|
|
@ -43,14 +62,24 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
'Psr\\SimpleCache\\' => 16,
|
'Psr\\SimpleCache\\' => 16,
|
||||||
'Psr\\Log\\' => 8,
|
'Psr\\Log\\' => 8,
|
||||||
'Psr\\Http\\Message\\' => 17,
|
'Psr\\Http\\Message\\' => 17,
|
||||||
|
'Psr\\EventDispatcher\\' => 20,
|
||||||
'Psr\\Container\\' => 14,
|
'Psr\\Container\\' => 14,
|
||||||
'Psr\\Cache\\' => 10,
|
'Psr\\Cache\\' => 10,
|
||||||
'PHPMailer\\PHPMailer\\' => 20,
|
'PHPMailer\\PHPMailer\\' => 20,
|
||||||
),
|
),
|
||||||
|
'L' =>
|
||||||
|
array (
|
||||||
|
'League\\MimeTypeDetection\\' => 25,
|
||||||
|
'League\\Flysystem\\' => 17,
|
||||||
|
),
|
||||||
'F' =>
|
'F' =>
|
||||||
array (
|
array (
|
||||||
'FormBuilder\\' => 12,
|
'FormBuilder\\' => 12,
|
||||||
),
|
),
|
||||||
|
'E' =>
|
||||||
|
array (
|
||||||
|
'Egulias\\EmailValidator\\' => 23,
|
||||||
|
),
|
||||||
'D' =>
|
'D' =>
|
||||||
array (
|
array (
|
||||||
'Doctrine\\Common\\Lexer\\' => 22,
|
'Doctrine\\Common\\Lexer\\' => 22,
|
||||||
|
|
@ -63,6 +92,10 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/topthink/think-view/src',
|
0 => __DIR__ . '/..' . '/topthink/think-view/src',
|
||||||
),
|
),
|
||||||
|
'think\\composer\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/topthink/think-installer/src',
|
||||||
|
),
|
||||||
'think\\captcha\\' =>
|
'think\\captcha\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/topthink/think-captcha/src',
|
0 => __DIR__ . '/..' . '/topthink/think-captcha/src',
|
||||||
|
|
@ -74,9 +107,22 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
'think\\' =>
|
'think\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/topthink/framework/src/think',
|
0 => __DIR__ . '/..' . '/topthink/framework/src/think',
|
||||||
1 => __DIR__ . '/..' . '/topthink/think-helper/src',
|
1 => __DIR__ . '/..' . '/topthink/think-filesystem/src',
|
||||||
2 => __DIR__ . '/..' . '/topthink/think-orm/src',
|
2 => __DIR__ . '/..' . '/topthink/think-helper/src',
|
||||||
3 => __DIR__ . '/..' . '/topthink/think-template/src',
|
3 => __DIR__ . '/..' . '/topthink/think-orm/src',
|
||||||
|
4 => __DIR__ . '/..' . '/topthink/think-template/src',
|
||||||
|
),
|
||||||
|
'mailer\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/yzh52521/think-mail/src/mailer',
|
||||||
|
),
|
||||||
|
'liliuwei\\social\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/liliuwei/thinkphp-social/src',
|
||||||
|
),
|
||||||
|
'liliuwei\\sitemap\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/liliuwei/php-sitemap/src',
|
||||||
),
|
),
|
||||||
'app\\' =>
|
'app\\' =>
|
||||||
array (
|
array (
|
||||||
|
|
@ -94,14 +140,42 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
|
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
|
||||||
),
|
),
|
||||||
|
'Symfony\\Polyfill\\Intl\\Normalizer\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer',
|
||||||
|
),
|
||||||
|
'Symfony\\Polyfill\\Intl\\Idn\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/polyfill-intl-idn',
|
||||||
|
),
|
||||||
|
'Symfony\\Contracts\\Service\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/service-contracts',
|
||||||
|
),
|
||||||
|
'Symfony\\Contracts\\EventDispatcher\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
|
||||||
|
),
|
||||||
'Symfony\\Component\\VarDumper\\' =>
|
'Symfony\\Component\\VarDumper\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/var-dumper',
|
0 => __DIR__ . '/..' . '/symfony/var-dumper',
|
||||||
),
|
),
|
||||||
|
'Symfony\\Component\\Mime\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/mime',
|
||||||
|
),
|
||||||
|
'Symfony\\Component\\Mailer\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/mailer',
|
||||||
|
),
|
||||||
'Symfony\\Component\\HttpFoundation\\' =>
|
'Symfony\\Component\\HttpFoundation\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/symfony/http-foundation',
|
0 => __DIR__ . '/..' . '/symfony/http-foundation',
|
||||||
),
|
),
|
||||||
|
'Symfony\\Component\\EventDispatcher\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
|
||||||
|
),
|
||||||
'Spatie\\Macroable\\' =>
|
'Spatie\\Macroable\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/spatie/macroable/src',
|
0 => __DIR__ . '/..' . '/spatie/macroable/src',
|
||||||
|
|
@ -118,6 +192,10 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/psr/http-message/src',
|
0 => __DIR__ . '/..' . '/psr/http-message/src',
|
||||||
),
|
),
|
||||||
|
'Psr\\EventDispatcher\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
|
||||||
|
),
|
||||||
'Psr\\Container\\' =>
|
'Psr\\Container\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/psr/container/src',
|
0 => __DIR__ . '/..' . '/psr/container/src',
|
||||||
|
|
@ -130,10 +208,22 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
|
0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
|
||||||
),
|
),
|
||||||
|
'League\\MimeTypeDetection\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/league/mime-type-detection/src',
|
||||||
|
),
|
||||||
|
'League\\Flysystem\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/league/flysystem/src',
|
||||||
|
),
|
||||||
'FormBuilder\\' =>
|
'FormBuilder\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/xaboy/form-builder/src',
|
0 => __DIR__ . '/..' . '/xaboy/form-builder/src',
|
||||||
),
|
),
|
||||||
|
'Egulias\\EmailValidator\\' =>
|
||||||
|
array (
|
||||||
|
0 => __DIR__ . '/..' . '/egulias/email-validator/src',
|
||||||
|
),
|
||||||
'Doctrine\\Common\\Lexer\\' =>
|
'Doctrine\\Common\\Lexer\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
|
0 => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer',
|
||||||
|
|
@ -151,6 +241,7 @@ class ComposerStaticInit4b57298e8d0e895486f3307a354a7e1a
|
||||||
public static $classMap = array (
|
public static $classMap = array (
|
||||||
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
|
'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php',
|
||||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
|
'Normalizer' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/Resources/stubs/Normalizer.php',
|
||||||
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||||
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
|
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
|
||||||
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,7 +3,7 @@
|
||||||
'name' => 'topthink/think',
|
'name' => 'topthink/think',
|
||||||
'pretty_version' => 'dev-master',
|
'pretty_version' => 'dev-master',
|
||||||
'version' => 'dev-master',
|
'version' => 'dev-master',
|
||||||
'reference' => '79c3c9ec53e5564fa6f2d96d7bc96fecde590cf8',
|
'reference' => 'e3cf01d01f242927ac3fd497b0cbc88fad1c9df2',
|
||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
@ -28,6 +28,51 @@
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'egulias/email-validator' => array(
|
||||||
|
'pretty_version' => '3.2.1',
|
||||||
|
'version' => '3.2.1.0',
|
||||||
|
'reference' => 'f88dcf4b14af14a98ad96b14b2b317969eab6715',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../egulias/email-validator',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'league/flysystem' => array(
|
||||||
|
'pretty_version' => '2.5.0',
|
||||||
|
'version' => '2.5.0.0',
|
||||||
|
'reference' => '8aaffb653c5777781b0f7f69a5d937baf7ab6cdb',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../league/flysystem',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'league/mime-type-detection' => array(
|
||||||
|
'pretty_version' => '1.11.0',
|
||||||
|
'version' => '1.11.0.0',
|
||||||
|
'reference' => 'ff6248ea87a9f116e78edd6002e39e5128a0d4dd',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../league/mime-type-detection',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'liliuwei/php-sitemap' => array(
|
||||||
|
'pretty_version' => 'v1.0.0',
|
||||||
|
'version' => '1.0.0.0',
|
||||||
|
'reference' => 'a448661d333cfea03cdb7c19ac657dc3430ab462',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../liliuwei/php-sitemap',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'liliuwei/thinkphp-social' => array(
|
||||||
|
'pretty_version' => 'v1.3',
|
||||||
|
'version' => '1.3.0.0',
|
||||||
|
'reference' => '2067fc2c2cc3b3d109602bc19c3e5a99c5f4c970',
|
||||||
|
'type' => 'think-extend',
|
||||||
|
'install_path' => __DIR__ . '/../liliuwei/thinkphp-social',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'phpmailer/phpmailer' => array(
|
'phpmailer/phpmailer' => array(
|
||||||
'pretty_version' => 'v6.6.4',
|
'pretty_version' => 'v6.6.4',
|
||||||
'version' => '6.6.4.0',
|
'version' => '6.6.4.0',
|
||||||
|
|
@ -55,6 +100,21 @@
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'psr/event-dispatcher' => array(
|
||||||
|
'pretty_version' => '1.0.0',
|
||||||
|
'version' => '1.0.0.0',
|
||||||
|
'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../psr/event-dispatcher',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'psr/event-dispatcher-implementation' => array(
|
||||||
|
'dev_requirement' => false,
|
||||||
|
'provided' => array(
|
||||||
|
0 => '1.0',
|
||||||
|
),
|
||||||
|
),
|
||||||
'psr/http-message' => array(
|
'psr/http-message' => array(
|
||||||
'pretty_version' => '1.0.1',
|
'pretty_version' => '1.0.1',
|
||||||
'version' => '1.0.1.0',
|
'version' => '1.0.1.0',
|
||||||
|
|
@ -100,6 +160,30 @@
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'symfony/event-dispatcher' => array(
|
||||||
|
'pretty_version' => 'v5.4.9',
|
||||||
|
'version' => '5.4.9.0',
|
||||||
|
'reference' => '8e6ce1cc0279e3ff3c8ff0f43813bc88d21ca1bc',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/event-dispatcher',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'symfony/event-dispatcher-contracts' => array(
|
||||||
|
'pretty_version' => 'v2.5.2',
|
||||||
|
'version' => '2.5.2.0',
|
||||||
|
'reference' => 'f98b54df6ad059855739db6fcbc2d36995283fe1',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'symfony/event-dispatcher-implementation' => array(
|
||||||
|
'dev_requirement' => false,
|
||||||
|
'provided' => array(
|
||||||
|
0 => '2.0',
|
||||||
|
),
|
||||||
|
),
|
||||||
'symfony/http-foundation' => array(
|
'symfony/http-foundation' => array(
|
||||||
'pretty_version' => 'v5.4.15',
|
'pretty_version' => 'v5.4.15',
|
||||||
'version' => '5.4.15.0',
|
'version' => '5.4.15.0',
|
||||||
|
|
@ -109,37 +193,82 @@
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'symfony/mailer' => array(
|
||||||
|
'pretty_version' => 'v5.4.15',
|
||||||
|
'version' => '5.4.15.0',
|
||||||
|
'reference' => '926f4deddb60d40024e6058fd8f94e70e4024930',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/mailer',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'symfony/mime' => array(
|
||||||
|
'pretty_version' => 'v5.4.14',
|
||||||
|
'version' => '5.4.14.0',
|
||||||
|
'reference' => '1c118b253bb3495d81e95a6e3ec6c2766a98a0c4',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/mime',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'symfony/polyfill-intl-idn' => array(
|
||||||
|
'pretty_version' => 'v1.27.0',
|
||||||
|
'version' => '1.27.0.0',
|
||||||
|
'reference' => '639084e360537a19f9ee352433b84ce831f3d2da',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/polyfill-intl-idn',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
'symfony/polyfill-intl-normalizer' => array(
|
||||||
|
'pretty_version' => 'v1.27.0',
|
||||||
|
'version' => '1.27.0.0',
|
||||||
|
'reference' => '19bd1e4fcd5b91116f14d8533c57831ed00571b6',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/polyfill-intl-normalizer',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'symfony/polyfill-mbstring' => array(
|
'symfony/polyfill-mbstring' => array(
|
||||||
'pretty_version' => 'v1.26.0',
|
'pretty_version' => 'v1.27.0',
|
||||||
'version' => '1.26.0.0',
|
'version' => '1.27.0.0',
|
||||||
'reference' => '9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e',
|
'reference' => '8ad114f6b39e2c98a8b0e3bd907732c207c2b534',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
|
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
'symfony/polyfill-php72' => array(
|
'symfony/polyfill-php72' => array(
|
||||||
'pretty_version' => 'v1.26.0',
|
'pretty_version' => 'v1.27.0',
|
||||||
'version' => '1.26.0.0',
|
'version' => '1.27.0.0',
|
||||||
'reference' => 'bf44a9fd41feaac72b074de600314a93e2ae78e2',
|
'reference' => '869329b1e9894268a8a61dabb69153029b7a8c97',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../symfony/polyfill-php72',
|
'install_path' => __DIR__ . '/../symfony/polyfill-php72',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => true,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
'symfony/polyfill-php80' => array(
|
'symfony/polyfill-php80' => array(
|
||||||
'pretty_version' => 'v1.26.0',
|
'pretty_version' => 'v1.27.0',
|
||||||
'version' => '1.26.0.0',
|
'version' => '1.27.0.0',
|
||||||
'reference' => 'cfa0ae98841b9e461207c13ab093d76b0fa7bace',
|
'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
|
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'symfony/service-contracts' => array(
|
||||||
|
'pretty_version' => 'v2.5.2',
|
||||||
|
'version' => '2.5.2.0',
|
||||||
|
'reference' => '4b426aac47d6427cc1a1d0f7e2ac724627f5966c',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../symfony/service-contracts',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'symfony/var-dumper' => array(
|
'symfony/var-dumper' => array(
|
||||||
'pretty_version' => 'v4.4.46',
|
'pretty_version' => 'v4.4.47',
|
||||||
'version' => '4.4.46.0',
|
'version' => '4.4.47.0',
|
||||||
'reference' => '90425fd98d1ecad98e4b2dca9f54f62069193b15',
|
'reference' => '1069c7a3fca74578022fab6f81643248d02f8e63',
|
||||||
'type' => 'library',
|
'type' => 'library',
|
||||||
'install_path' => __DIR__ . '/../symfony/var-dumper',
|
'install_path' => __DIR__ . '/../symfony/var-dumper',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
@ -157,7 +286,7 @@
|
||||||
'topthink/think' => array(
|
'topthink/think' => array(
|
||||||
'pretty_version' => 'dev-master',
|
'pretty_version' => 'dev-master',
|
||||||
'version' => 'dev-master',
|
'version' => 'dev-master',
|
||||||
'reference' => '79c3c9ec53e5564fa6f2d96d7bc96fecde590cf8',
|
'reference' => 'e3cf01d01f242927ac3fd497b0cbc88fad1c9df2',
|
||||||
'type' => 'project',
|
'type' => 'project',
|
||||||
'install_path' => __DIR__ . '/../../',
|
'install_path' => __DIR__ . '/../../',
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
|
|
@ -172,6 +301,15 @@
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'topthink/think-filesystem' => array(
|
||||||
|
'pretty_version' => 'v2.0.0',
|
||||||
|
'version' => '2.0.0.0',
|
||||||
|
'reference' => '63e525fd74f451b2df1df060c3194e9b6e724730',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../topthink/think-filesystem',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'topthink/think-helper' => array(
|
'topthink/think-helper' => array(
|
||||||
'pretty_version' => 'v3.1.6',
|
'pretty_version' => 'v3.1.6',
|
||||||
'version' => '3.1.6.0',
|
'version' => '3.1.6.0',
|
||||||
|
|
@ -181,6 +319,15 @@
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'topthink/think-installer' => array(
|
||||||
|
'pretty_version' => 'v2.0.5',
|
||||||
|
'version' => '2.0.5.0',
|
||||||
|
'reference' => '38ba647706e35d6704b5d370c06f8a160b635f88',
|
||||||
|
'type' => 'composer-plugin',
|
||||||
|
'install_path' => __DIR__ . '/../topthink/think-installer',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
'topthink/think-multi-app' => array(
|
'topthink/think-multi-app' => array(
|
||||||
'pretty_version' => 'v1.0.15',
|
'pretty_version' => 'v1.0.15',
|
||||||
'version' => '1.0.15.0',
|
'version' => '1.0.15.0',
|
||||||
|
|
@ -226,5 +373,14 @@
|
||||||
'aliases' => array(),
|
'aliases' => array(),
|
||||||
'dev_requirement' => false,
|
'dev_requirement' => false,
|
||||||
),
|
),
|
||||||
|
'yzh52521/think-mail' => array(
|
||||||
|
'pretty_version' => 'v3.0.2',
|
||||||
|
'version' => '3.0.2.0',
|
||||||
|
'reference' => '51535665a75d8324c1301e5a11f116411ea73355',
|
||||||
|
'type' => 'library',
|
||||||
|
'install_path' => __DIR__ . '/../yzh52521/think-mail',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
# EmailValidator v3 Changelog
|
||||||
|
|
||||||
|
## New Features
|
||||||
|
|
||||||
|
* Access to local part and domain part from EmailParser
|
||||||
|
* Validations outside of the scope of the RFC will be considered "extra" validations, thus opening the door for adding new; will live in their own folder "extra" (as requested in #248, #195, #183).
|
||||||
|
|
||||||
|
## Breacking changes
|
||||||
|
|
||||||
|
* PHP version upgraded to match Symfony's (as of 12/2020).
|
||||||
|
* DNSCheckValidation now fails for missing MX records. While the RFC argues that the existence of only A records to be valid, starting in v3 they will be considered invalid.
|
||||||
|
* Emails domain part are now intenteded to be RFC 1035 compliant, rendering previous valid emails (e.g example@examp&) invalid.
|
||||||
|
|
||||||
|
## PHP versions upgrade policy
|
||||||
|
PHP version upgrade requirement will happen via MINOR (3.x) version upgrades of the library, following the adoption level by major frameworks.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
* #235
|
||||||
|
* #215
|
||||||
|
* #130
|
||||||
|
* #258
|
||||||
|
* #188
|
||||||
|
* #181
|
||||||
|
* #217
|
||||||
|
* #214
|
||||||
|
* #249
|
||||||
|
* #236
|
||||||
|
* #257
|
||||||
|
* #210
|
||||||
|
|
||||||
|
## Thanks
|
||||||
|
To contributors, be it with PRs, reporting issues or supporting otherwise.
|
||||||
|
|
||||||
|
|
@ -0,0 +1,153 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
When contributing to this repository make sure to follow the Pull request process below.
|
||||||
|
Reduce to the minimum 3rd party dependencies.
|
||||||
|
|
||||||
|
Please note we have a [code of conduct](#Code of Conduct), please follow it in all your interactions with the project.
|
||||||
|
|
||||||
|
## Pull Request Process
|
||||||
|
|
||||||
|
When doing a PR to v2 remember that you also have to do the PR port to v3, or tests confirming the bug is not reproducible.
|
||||||
|
|
||||||
|
1. Supported version is v3. If you are fixing a bug in v2, please port to v3
|
||||||
|
2. Use the title as a brief description of the changes
|
||||||
|
3. Describe the changes you are proposing
|
||||||
|
1. If adding an extra validation state the benefits of adding it and the problem is solving
|
||||||
|
2. Document in the readme, by adding it to the list
|
||||||
|
4. Provide appropriate tests for the code you are submitting: aim to keep the existing coverage percentage.
|
||||||
|
5. Add your Twitter handle (if you have) so we can thank you there.
|
||||||
|
|
||||||
|
## License
|
||||||
|
By contributing, you agree that your contributions will be licensed under its MIT License.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
### Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity
|
||||||
|
and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
|
diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
### Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for our
|
||||||
|
community include:
|
||||||
|
|
||||||
|
* Demonstrating empathy and kindness toward other people
|
||||||
|
* Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
* Giving and gracefully accepting constructive feedback
|
||||||
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
|
and learning from the experience
|
||||||
|
* Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
* The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
* Public or private harassment
|
||||||
|
* Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
|
professional setting
|
||||||
|
|
||||||
|
### Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our standards of
|
||||||
|
acceptable behavior and will take appropriate and fair corrective action in
|
||||||
|
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||||
|
or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit, or reject
|
||||||
|
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||||
|
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||||
|
decisions when appropriate.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
|
an individual is officially representing the community in public spaces.
|
||||||
|
Examples of representing our community include using an official e-mail address,
|
||||||
|
posting via an official social media account, or acting as an appointed
|
||||||
|
representative at an online or offline event.
|
||||||
|
|
||||||
|
### Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the community leaders responsible for enforcement at <emailvalidatorrfc.ccreport@gmail.com>.
|
||||||
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
|
reporter of any incident.
|
||||||
|
|
||||||
|
#### Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in determining
|
||||||
|
the consequences for any action they deem in violation of this Code of Conduct:
|
||||||
|
|
||||||
|
#### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||||
|
unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders, providing
|
||||||
|
clarity around the nature of the violation and an explanation of why the
|
||||||
|
behavior was inappropriate. A public apology may be requested.
|
||||||
|
|
||||||
|
#### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
|
interaction with the people involved, including unsolicited interaction with
|
||||||
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
|
like social media. Violating these terms may lead to a temporary or
|
||||||
|
permanent ban.
|
||||||
|
|
||||||
|
#### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards, including
|
||||||
|
sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or public
|
||||||
|
communication with the community for a specified period of time. No public or
|
||||||
|
private interaction with the people involved, including unsolicited interaction
|
||||||
|
with those enforcing the Code of Conduct, is allowed during this period.
|
||||||
|
Violating these terms may lead to a permanent ban.
|
||||||
|
|
||||||
|
#### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of community
|
||||||
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction within
|
||||||
|
the community.
|
||||||
|
|
||||||
|
### Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
|
version 2.0, available at
|
||||||
|
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by
|
||||||
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available
|
||||||
|
at [https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2013-2021 Eduardo Gulias Davis
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
{
|
||||||
|
"name": "egulias/email-validator",
|
||||||
|
"description": "A library for validating emails against several RFCs",
|
||||||
|
"homepage": "https://github.com/egulias/EmailValidator",
|
||||||
|
"keywords": ["email", "validation", "validator", "emailvalidation", "emailvalidator"],
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{"name": "Eduardo Gulias Davis"}
|
||||||
|
],
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.0.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
|
"doctrine/lexer": "^1.2",
|
||||||
|
"symfony/polyfill-intl-idn": "^1.15"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"php-coveralls/php-coveralls": "^2.2",
|
||||||
|
"phpunit/phpunit": "^8.5.8|^9.3.3",
|
||||||
|
"vimeo/psalm": "^4"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Egulias\\EmailValidator\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Egulias\\EmailValidator\\Tests\\": "tests"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,339 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator;
|
||||||
|
|
||||||
|
use Doctrine\Common\Lexer\AbstractLexer;
|
||||||
|
|
||||||
|
class EmailLexer extends AbstractLexer
|
||||||
|
{
|
||||||
|
//ASCII values
|
||||||
|
const S_EMPTY = null;
|
||||||
|
const C_NUL = 0;
|
||||||
|
const S_HTAB = 9;
|
||||||
|
const S_LF = 10;
|
||||||
|
const S_CR = 13;
|
||||||
|
const S_SP = 32;
|
||||||
|
const EXCLAMATION = 33;
|
||||||
|
const S_DQUOTE = 34;
|
||||||
|
const NUMBER_SIGN = 35;
|
||||||
|
const DOLLAR = 36;
|
||||||
|
const PERCENTAGE = 37;
|
||||||
|
const AMPERSAND = 38;
|
||||||
|
const S_SQUOTE = 39;
|
||||||
|
const S_OPENPARENTHESIS = 40;
|
||||||
|
const S_CLOSEPARENTHESIS = 41;
|
||||||
|
const ASTERISK = 42;
|
||||||
|
const S_PLUS = 43;
|
||||||
|
const S_COMMA = 44;
|
||||||
|
const S_HYPHEN = 45;
|
||||||
|
const S_DOT = 46;
|
||||||
|
const S_SLASH = 47;
|
||||||
|
const S_COLON = 58;
|
||||||
|
const S_SEMICOLON = 59;
|
||||||
|
const S_LOWERTHAN = 60;
|
||||||
|
const S_EQUAL = 61;
|
||||||
|
const S_GREATERTHAN = 62;
|
||||||
|
const QUESTIONMARK = 63;
|
||||||
|
const S_AT = 64;
|
||||||
|
const S_OPENBRACKET = 91;
|
||||||
|
const S_BACKSLASH = 92;
|
||||||
|
const S_CLOSEBRACKET = 93;
|
||||||
|
const CARET = 94;
|
||||||
|
const S_UNDERSCORE = 95;
|
||||||
|
const S_BACKTICK = 96;
|
||||||
|
const S_OPENCURLYBRACES = 123;
|
||||||
|
const S_PIPE = 124;
|
||||||
|
const S_CLOSECURLYBRACES = 125;
|
||||||
|
const S_TILDE = 126;
|
||||||
|
const C_DEL = 127;
|
||||||
|
const INVERT_QUESTIONMARK= 168;
|
||||||
|
const INVERT_EXCLAMATION = 173;
|
||||||
|
const GENERIC = 300;
|
||||||
|
const S_IPV6TAG = 301;
|
||||||
|
const INVALID = 302;
|
||||||
|
const CRLF = 1310;
|
||||||
|
const S_DOUBLECOLON = 5858;
|
||||||
|
const ASCII_INVALID_FROM = 127;
|
||||||
|
const ASCII_INVALID_TO = 199;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* US-ASCII visible characters not valid for atext (@link http://tools.ietf.org/html/rfc5322#section-3.2.3)
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $charValue = [
|
||||||
|
'{' => self::S_OPENCURLYBRACES,
|
||||||
|
'}' => self::S_CLOSECURLYBRACES,
|
||||||
|
'(' => self::S_OPENPARENTHESIS,
|
||||||
|
')' => self::S_CLOSEPARENTHESIS,
|
||||||
|
'<' => self::S_LOWERTHAN,
|
||||||
|
'>' => self::S_GREATERTHAN,
|
||||||
|
'[' => self::S_OPENBRACKET,
|
||||||
|
']' => self::S_CLOSEBRACKET,
|
||||||
|
':' => self::S_COLON,
|
||||||
|
';' => self::S_SEMICOLON,
|
||||||
|
'@' => self::S_AT,
|
||||||
|
'\\' => self::S_BACKSLASH,
|
||||||
|
'/' => self::S_SLASH,
|
||||||
|
',' => self::S_COMMA,
|
||||||
|
'.' => self::S_DOT,
|
||||||
|
"'" => self::S_SQUOTE,
|
||||||
|
"`" => self::S_BACKTICK,
|
||||||
|
'"' => self::S_DQUOTE,
|
||||||
|
'-' => self::S_HYPHEN,
|
||||||
|
'::' => self::S_DOUBLECOLON,
|
||||||
|
' ' => self::S_SP,
|
||||||
|
"\t" => self::S_HTAB,
|
||||||
|
"\r" => self::S_CR,
|
||||||
|
"\n" => self::S_LF,
|
||||||
|
"\r\n" => self::CRLF,
|
||||||
|
'IPv6' => self::S_IPV6TAG,
|
||||||
|
'' => self::S_EMPTY,
|
||||||
|
'\0' => self::C_NUL,
|
||||||
|
'*' => self::ASTERISK,
|
||||||
|
'!' => self::EXCLAMATION,
|
||||||
|
'&' => self::AMPERSAND,
|
||||||
|
'^' => self::CARET,
|
||||||
|
'$' => self::DOLLAR,
|
||||||
|
'%' => self::PERCENTAGE,
|
||||||
|
'~' => self::S_TILDE,
|
||||||
|
'|' => self::S_PIPE,
|
||||||
|
'_' => self::S_UNDERSCORE,
|
||||||
|
'=' => self::S_EQUAL,
|
||||||
|
'+' => self::S_PLUS,
|
||||||
|
'¿' => self::INVERT_QUESTIONMARK,
|
||||||
|
'?' => self::QUESTIONMARK,
|
||||||
|
'#' => self::NUMBER_SIGN,
|
||||||
|
'¡' => self::INVERT_EXCLAMATION,
|
||||||
|
];
|
||||||
|
|
||||||
|
const INVALID_CHARS_REGEX = "/[^\p{S}\p{C}\p{Cc}]+/iu";
|
||||||
|
|
||||||
|
const VALID_UTF8_REGEX = '/\p{Cc}+/u';
|
||||||
|
|
||||||
|
const CATCHABLE_PATTERNS = [
|
||||||
|
'[a-zA-Z]+[46]?', //ASCII and domain literal
|
||||||
|
'[^\x00-\x7F]', //UTF-8
|
||||||
|
'[0-9]+',
|
||||||
|
'\r\n',
|
||||||
|
'::',
|
||||||
|
'\s+?',
|
||||||
|
'.',
|
||||||
|
];
|
||||||
|
|
||||||
|
const NON_CATCHABLE_PATTERNS = [
|
||||||
|
'[\xA0-\xff]+',
|
||||||
|
];
|
||||||
|
|
||||||
|
const MODIFIERS = 'iu';
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $hasInvalidTokens = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
* @psalm-var array{value:string, type:null|int, position:int}|array<empty, empty>
|
||||||
|
*/
|
||||||
|
protected $previous = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last matched/seen token.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*
|
||||||
|
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||||
|
* @psalm-var array{value:string, type:null|int, position:int}
|
||||||
|
* @psalm-suppress NonInvariantDocblockPropertyType
|
||||||
|
*/
|
||||||
|
public $token;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The next token in the input.
|
||||||
|
*
|
||||||
|
* @var array{position: int, type: int|null|string, value: int|string}|null
|
||||||
|
*/
|
||||||
|
public $lookahead;
|
||||||
|
|
||||||
|
/** @psalm-var array{value:'', type:null, position:0} */
|
||||||
|
private static $nullToken = [
|
||||||
|
'value' => '',
|
||||||
|
'type' => null,
|
||||||
|
'position' => 0,
|
||||||
|
];
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
private $accumulator = '';
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
private $hasToRecord = false;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->previous = $this->token = self::$nullToken;
|
||||||
|
$this->lookahead = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reset() : void
|
||||||
|
{
|
||||||
|
$this->hasInvalidTokens = false;
|
||||||
|
parent::reset();
|
||||||
|
$this->previous = $this->token = self::$nullToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $type
|
||||||
|
* @throws \UnexpectedValueException
|
||||||
|
* @return boolean
|
||||||
|
*
|
||||||
|
* @psalm-suppress InvalidScalarArgument
|
||||||
|
*/
|
||||||
|
public function find($type) : bool
|
||||||
|
{
|
||||||
|
$search = clone $this;
|
||||||
|
$search->skipUntil($type);
|
||||||
|
|
||||||
|
if (!$search->lookahead) {
|
||||||
|
throw new \UnexpectedValueException($type . ' not found');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* moveNext
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function moveNext() : bool
|
||||||
|
{
|
||||||
|
if ($this->hasToRecord && $this->previous === self::$nullToken) {
|
||||||
|
$this->accumulator .= $this->token['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->previous = $this->token;
|
||||||
|
|
||||||
|
if($this->lookahead === null) {
|
||||||
|
$this->lookahead = self::$nullToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
$hasNext = parent::moveNext();
|
||||||
|
|
||||||
|
if ($this->hasToRecord) {
|
||||||
|
$this->accumulator .= $this->token['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $hasNext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve token type. Also processes the token value if necessary.
|
||||||
|
*
|
||||||
|
* @param string $value
|
||||||
|
* @throws \InvalidArgumentException
|
||||||
|
* @return integer
|
||||||
|
*/
|
||||||
|
protected function getType(&$value)
|
||||||
|
{
|
||||||
|
$encoded = $value;
|
||||||
|
|
||||||
|
if (mb_detect_encoding($value, 'auto', true) !== 'UTF-8') {
|
||||||
|
$encoded = mb_convert_encoding($value, 'UTF-8', 'Windows-1252');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isValid($encoded)) {
|
||||||
|
return $this->charValue[$encoded];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isNullType($encoded)) {
|
||||||
|
return self::C_NUL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->isInvalidChar($encoded)) {
|
||||||
|
$this->hasInvalidTokens = true;
|
||||||
|
return self::INVALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return self::GENERIC;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isValid(string $value) : bool
|
||||||
|
{
|
||||||
|
return isset($this->charValue[$value]);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isNullType(string $value) : bool
|
||||||
|
{
|
||||||
|
return $value === "\0";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isInvalidChar(string $value) : bool
|
||||||
|
{
|
||||||
|
return !preg_match(self::INVALID_CHARS_REGEX, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isUTF8Invalid(string $value) : bool
|
||||||
|
{
|
||||||
|
return preg_match(self::VALID_UTF8_REGEX, $value) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasInvalidTokens() : bool
|
||||||
|
{
|
||||||
|
return $this->hasInvalidTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getPrevious
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getPrevious() : array
|
||||||
|
{
|
||||||
|
return $this->previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lexical catchable patterns.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
protected function getCatchablePatterns() : array
|
||||||
|
{
|
||||||
|
return self::CATCHABLE_PATTERNS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lexical non-catchable patterns.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
*/
|
||||||
|
protected function getNonCatchablePatterns() : array
|
||||||
|
{
|
||||||
|
return self::NON_CATCHABLE_PATTERNS;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getModifiers() : string
|
||||||
|
{
|
||||||
|
return self::MODIFIERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAccumulatedValues() : string
|
||||||
|
{
|
||||||
|
return $this->accumulator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startRecording() : void
|
||||||
|
{
|
||||||
|
$this->hasToRecord = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stopRecording() : void
|
||||||
|
{
|
||||||
|
$this->hasToRecord = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clearRecorded() : void
|
||||||
|
{
|
||||||
|
$this->accumulator = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Parser\LocalPart;
|
||||||
|
use Egulias\EmailValidator\Parser\DomainPart;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Warning\EmailTooLong;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\NoLocalPart;
|
||||||
|
|
||||||
|
class EmailParser extends Parser
|
||||||
|
{
|
||||||
|
const EMAIL_MAX_LENGTH = 254;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $domainPart = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $localPart = '';
|
||||||
|
|
||||||
|
public function parse(string $str) : Result
|
||||||
|
{
|
||||||
|
$result = parent::parse($str);
|
||||||
|
|
||||||
|
$this->addLongEmailWarning($this->localPart, $this->domainPart);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function preLeftParsing(): Result
|
||||||
|
{
|
||||||
|
if (!$this->hasAtToken()) {
|
||||||
|
return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]);
|
||||||
|
}
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseLeftFromAt(): Result
|
||||||
|
{
|
||||||
|
return $this->processLocalPart();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseRightFromAt(): Result
|
||||||
|
{
|
||||||
|
return $this->processDomainPart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processLocalPart() : Result
|
||||||
|
{
|
||||||
|
$localPartParser = new LocalPart($this->lexer);
|
||||||
|
$localPartResult = $localPartParser->parse();
|
||||||
|
$this->localPart = $localPartParser->localPart();
|
||||||
|
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);
|
||||||
|
|
||||||
|
return $localPartResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processDomainPart() : Result
|
||||||
|
{
|
||||||
|
$domainPartParser = new DomainPart($this->lexer);
|
||||||
|
$domainPartResult = $domainPartParser->parse();
|
||||||
|
$this->domainPart = $domainPartParser->domainPart();
|
||||||
|
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);
|
||||||
|
|
||||||
|
return $domainPartResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDomainPart() : string
|
||||||
|
{
|
||||||
|
return $this->domainPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLocalPart() : string
|
||||||
|
{
|
||||||
|
return $this->localPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void
|
||||||
|
{
|
||||||
|
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAIL_MAX_LENGTH) {
|
||||||
|
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Validation\EmailValidation;
|
||||||
|
|
||||||
|
class EmailValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var EmailLexer
|
||||||
|
*/
|
||||||
|
private $lexer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Warning\Warning[]
|
||||||
|
*/
|
||||||
|
private $warnings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ?InvalidEmail
|
||||||
|
*/
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->lexer = new EmailLexer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $email
|
||||||
|
* @param EmailValidation $emailValidation
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isValid(string $email, EmailValidation $emailValidation)
|
||||||
|
{
|
||||||
|
$isValid = $emailValidation->isValid($email, $this->lexer);
|
||||||
|
$this->warnings = $emailValidation->getWarnings();
|
||||||
|
$this->error = $emailValidation->getError();
|
||||||
|
|
||||||
|
return $isValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function hasWarnings()
|
||||||
|
{
|
||||||
|
return !empty($this->warnings);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getWarnings()
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InvalidEmail|null
|
||||||
|
*/
|
||||||
|
public function getError()
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\Parser;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Parser\IDLeftPart;
|
||||||
|
use Egulias\EmailValidator\Parser\IDRightPart;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Warning\EmailTooLong;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\NoLocalPart;
|
||||||
|
|
||||||
|
class MessageIDParser extends Parser
|
||||||
|
{
|
||||||
|
|
||||||
|
const EMAILID_MAX_LENGTH = 254;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $idLeft = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $idRight = '';
|
||||||
|
|
||||||
|
public function parse(string $str) : Result
|
||||||
|
{
|
||||||
|
$result = parent::parse($str);
|
||||||
|
|
||||||
|
$this->addLongEmailWarning($this->idLeft, $this->idRight);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function preLeftParsing(): Result
|
||||||
|
{
|
||||||
|
if (!$this->hasAtToken()) {
|
||||||
|
return new InvalidEmail(new NoLocalPart(), $this->lexer->token["value"]);
|
||||||
|
}
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseLeftFromAt(): Result
|
||||||
|
{
|
||||||
|
return $this->processIDLeft();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseRightFromAt(): Result
|
||||||
|
{
|
||||||
|
return $this->processIDRight();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processIDLeft() : Result
|
||||||
|
{
|
||||||
|
$localPartParser = new IDLeftPart($this->lexer);
|
||||||
|
$localPartResult = $localPartParser->parse();
|
||||||
|
$this->idLeft = $localPartParser->localPart();
|
||||||
|
$this->warnings = array_merge($localPartParser->getWarnings(), $this->warnings);
|
||||||
|
|
||||||
|
return $localPartResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processIDRight() : Result
|
||||||
|
{
|
||||||
|
$domainPartParser = new IDRightPart($this->lexer);
|
||||||
|
$domainPartResult = $domainPartParser->parse();
|
||||||
|
$this->idRight = $domainPartParser->domainPart();
|
||||||
|
$this->warnings = array_merge($domainPartParser->getWarnings(), $this->warnings);
|
||||||
|
|
||||||
|
return $domainPartResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLeftPart() : string
|
||||||
|
{
|
||||||
|
return $this->idLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRightPart() : string
|
||||||
|
{
|
||||||
|
return $this->idRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addLongEmailWarning(string $localPart, string $parsedDomainPart) : void
|
||||||
|
{
|
||||||
|
if (strlen($localPart . '@' . $parsedDomainPart) > self::EMAILID_MAX_LENGTH) {
|
||||||
|
$this->warnings[EmailTooLong::CODE] = new EmailTooLong();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||||
|
|
||||||
|
abstract class Parser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Warning\Warning[]
|
||||||
|
*/
|
||||||
|
protected $warnings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var EmailLexer
|
||||||
|
*/
|
||||||
|
protected $lexer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* id-left "@" id-right
|
||||||
|
*/
|
||||||
|
abstract protected function parseRightFromAt() : Result;
|
||||||
|
abstract protected function parseLeftFromAt() : Result;
|
||||||
|
abstract protected function preLeftParsing() : Result;
|
||||||
|
|
||||||
|
|
||||||
|
public function __construct(EmailLexer $lexer)
|
||||||
|
{
|
||||||
|
$this->lexer = $lexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse(string $str) : Result
|
||||||
|
{
|
||||||
|
$this->lexer->setInput($str);
|
||||||
|
|
||||||
|
if ($this->lexer->hasInvalidTokens()) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT("Invalid tokens found"), $this->lexer->token["value"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$preParsingResult = $this->preLeftParsing();
|
||||||
|
if ($preParsingResult->isInvalid()) {
|
||||||
|
return $preParsingResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
$localPartResult = $this->parseLeftFromAt();
|
||||||
|
|
||||||
|
if ($localPartResult->isInvalid()) {
|
||||||
|
return $localPartResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
$domainPartResult = $this->parseRightFromAt();
|
||||||
|
|
||||||
|
if ($domainPartResult->isInvalid()) {
|
||||||
|
return $domainPartResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Warning\Warning[]
|
||||||
|
*/
|
||||||
|
public function getWarnings() : array
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function hasAtToken() : bool
|
||||||
|
{
|
||||||
|
$this->lexer->moveNext();
|
||||||
|
$this->lexer->moveNext();
|
||||||
|
|
||||||
|
return $this->lexer->token['type'] !== EmailLexer::S_AT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Warning\QuotedPart;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Parser\CommentStrategy\CommentStrategy;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\UnclosedComment;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\UnOpenedComment;
|
||||||
|
use Egulias\EmailValidator\Warning\Comment as WarningComment;
|
||||||
|
|
||||||
|
class Comment extends PartParser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $openedParenthesis = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var CommentStrategy
|
||||||
|
*/
|
||||||
|
private $commentStrategy;
|
||||||
|
|
||||||
|
public function __construct(EmailLexer $lexer, CommentStrategy $commentStrategy)
|
||||||
|
{
|
||||||
|
$this->lexer = $lexer;
|
||||||
|
$this->commentStrategy = $commentStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function parse() : Result
|
||||||
|
{
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
|
||||||
|
$this->openedParenthesis++;
|
||||||
|
if($this->noClosingParenthesis()) {
|
||||||
|
return new InvalidEmail(new UnclosedComment(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
|
||||||
|
return new InvalidEmail(new UnOpenedComment(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->warnings[WarningComment::CODE] = new WarningComment();
|
||||||
|
|
||||||
|
$moreTokens = true;
|
||||||
|
while ($this->commentStrategy->exitCondition($this->lexer, $this->openedParenthesis) && $moreTokens){
|
||||||
|
|
||||||
|
if ($this->lexer->isNextToken(EmailLexer::S_OPENPARENTHESIS)) {
|
||||||
|
$this->openedParenthesis++;
|
||||||
|
}
|
||||||
|
$this->warnEscaping();
|
||||||
|
if($this->lexer->isNextToken(EmailLexer::S_CLOSEPARENTHESIS)) {
|
||||||
|
$this->openedParenthesis--;
|
||||||
|
}
|
||||||
|
$moreTokens = $this->lexer->moveNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
if($this->openedParenthesis >= 1) {
|
||||||
|
return new InvalidEmail(new UnclosedComment(), $this->lexer->token['value']);
|
||||||
|
} else if ($this->openedParenthesis < 0) {
|
||||||
|
return new InvalidEmail(new UnOpenedComment(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$finalValidations = $this->commentStrategy->endOfLoopValidations($this->lexer);
|
||||||
|
|
||||||
|
$this->warnings = array_merge($this->warnings, $this->commentStrategy->getWarnings());
|
||||||
|
|
||||||
|
return $finalValidations;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
private function warnEscaping() : bool
|
||||||
|
{
|
||||||
|
//Backslash found
|
||||||
|
if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->warnings[QuotedPart::CODE] =
|
||||||
|
new QuotedPart($this->lexer->getPrevious()['type'], $this->lexer->token['type']);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function noClosingParenthesis() : bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->lexer->find(EmailLexer::S_CLOSEPARENTHESIS);
|
||||||
|
return false;
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
18
vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php
vendored
Normal file
18
vendor/egulias/email-validator/src/Parser/CommentStrategy/CommentStrategy.php
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser\CommentStrategy;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
|
||||||
|
interface CommentStrategy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Return "true" to continue, "false" to exit
|
||||||
|
*/
|
||||||
|
public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool;
|
||||||
|
|
||||||
|
public function endOfLoopValidations(EmailLexer $lexer) : Result;
|
||||||
|
|
||||||
|
public function getWarnings() : array;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser\CommentStrategy;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||||
|
|
||||||
|
class DomainComment implements CommentStrategy
|
||||||
|
{
|
||||||
|
public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool
|
||||||
|
{
|
||||||
|
if (($openedParenthesis === 0 && $lexer->isNextToken(EmailLexer::S_DOT))){ // || !$internalLexer->moveNext()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function endOfLoopValidations(EmailLexer $lexer) : Result
|
||||||
|
{
|
||||||
|
//test for end of string
|
||||||
|
if (!$lexer->isNextToken(EmailLexer::S_DOT)) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT('DOT not found near CLOSEPARENTHESIS'), $lexer->token['value']);
|
||||||
|
}
|
||||||
|
//add warning
|
||||||
|
//Address is valid within the message but cannot be used unmodified for the envelope
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWarnings(): array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser\CommentStrategy;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Warning\CFWSNearAt;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||||
|
|
||||||
|
class LocalComment implements CommentStrategy
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $warnings = [];
|
||||||
|
|
||||||
|
public function exitCondition(EmailLexer $lexer, int $openedParenthesis) : bool
|
||||||
|
{
|
||||||
|
return !$lexer->isNextToken(EmailLexer::S_AT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function endOfLoopValidations(EmailLexer $lexer) : Result
|
||||||
|
{
|
||||||
|
if (!$lexer->isNextToken(EmailLexer::S_AT)) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT('ATEX is not expected after closing comments'), $lexer->token['value']);
|
||||||
|
}
|
||||||
|
$this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWarnings(): array
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
<?php
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Warning\CFWSWithFWS;
|
||||||
|
use Egulias\EmailValidator\Warning\IPV6BadChar;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\CRNoLF;
|
||||||
|
use Egulias\EmailValidator\Warning\IPV6ColonEnd;
|
||||||
|
use Egulias\EmailValidator\Warning\IPV6MaxGroups;
|
||||||
|
use Egulias\EmailValidator\Warning\ObsoleteDTEXT;
|
||||||
|
use Egulias\EmailValidator\Warning\AddressLiteral;
|
||||||
|
use Egulias\EmailValidator\Warning\IPV6ColonStart;
|
||||||
|
use Egulias\EmailValidator\Warning\IPV6Deprecated;
|
||||||
|
use Egulias\EmailValidator\Warning\IPV6GroupCount;
|
||||||
|
use Egulias\EmailValidator\Warning\IPV6DoubleColon;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingDTEXT;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\UnusualElements;
|
||||||
|
use Egulias\EmailValidator\Warning\DomainLiteral as WarningDomainLiteral;
|
||||||
|
|
||||||
|
class DomainLiteral extends PartParser
|
||||||
|
{
|
||||||
|
const IPV4_REGEX = '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
|
||||||
|
|
||||||
|
const OBSOLETE_WARNINGS = [
|
||||||
|
EmailLexer::INVALID,
|
||||||
|
EmailLexer::C_DEL,
|
||||||
|
EmailLexer::S_LF,
|
||||||
|
EmailLexer::S_BACKSLASH
|
||||||
|
];
|
||||||
|
|
||||||
|
public function parse() : Result
|
||||||
|
{
|
||||||
|
$this->addTagWarnings();
|
||||||
|
|
||||||
|
$IPv6TAG = false;
|
||||||
|
$addressLiteral = '';
|
||||||
|
|
||||||
|
do {
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::C_NUL) {
|
||||||
|
return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addObsoleteWarnings();
|
||||||
|
|
||||||
|
if ($this->lexer->isNextTokenAny(array(EmailLexer::S_OPENBRACKET, EmailLexer::S_OPENBRACKET))) {
|
||||||
|
return new InvalidEmail(new ExpectingDTEXT(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->isNextTokenAny(
|
||||||
|
array(EmailLexer::S_HTAB, EmailLexer::S_SP, EmailLexer::CRLF)
|
||||||
|
)) {
|
||||||
|
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
|
||||||
|
$this->parseFWS();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->isNextToken(EmailLexer::S_CR)) {
|
||||||
|
return new InvalidEmail(new CRNoLF(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH) {
|
||||||
|
return new InvalidEmail(new UnusualElements($this->lexer->token['value']), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_IPV6TAG) {
|
||||||
|
$IPv6TAG = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_CLOSEBRACKET) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$addressLiteral .= $this->lexer->token['value'];
|
||||||
|
|
||||||
|
} while ($this->lexer->moveNext());
|
||||||
|
|
||||||
|
|
||||||
|
//Encapsulate
|
||||||
|
$addressLiteral = str_replace('[', '', $addressLiteral);
|
||||||
|
$isAddressLiteralIPv4 = $this->checkIPV4Tag($addressLiteral);
|
||||||
|
|
||||||
|
if (!$isAddressLiteralIPv4) {
|
||||||
|
return new ValidEmail();
|
||||||
|
} else {
|
||||||
|
$addressLiteral = $this->convertIPv4ToIPv6($addressLiteral);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$IPv6TAG) {
|
||||||
|
$this->warnings[WarningDomainLiteral::CODE] = new WarningDomainLiteral();
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->warnings[AddressLiteral::CODE] = new AddressLiteral();
|
||||||
|
|
||||||
|
$this->checkIPV6Tag($addressLiteral);
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $addressLiteral
|
||||||
|
* @param int $maxGroups
|
||||||
|
*/
|
||||||
|
public function checkIPV6Tag($addressLiteral, $maxGroups = 8) : void
|
||||||
|
{
|
||||||
|
$prev = $this->lexer->getPrevious();
|
||||||
|
if ($prev['type'] === EmailLexer::S_COLON) {
|
||||||
|
$this->warnings[IPV6ColonEnd::CODE] = new IPV6ColonEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
$IPv6 = substr($addressLiteral, 5);
|
||||||
|
//Daniel Marschall's new IPv6 testing strategy
|
||||||
|
$matchesIP = explode(':', $IPv6);
|
||||||
|
$groupCount = count($matchesIP);
|
||||||
|
$colons = strpos($IPv6, '::');
|
||||||
|
|
||||||
|
if (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) {
|
||||||
|
$this->warnings[IPV6BadChar::CODE] = new IPV6BadChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($colons === false) {
|
||||||
|
// We need exactly the right number of groups
|
||||||
|
if ($groupCount !== $maxGroups) {
|
||||||
|
$this->warnings[IPV6GroupCount::CODE] = new IPV6GroupCount();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($colons !== strrpos($IPv6, '::')) {
|
||||||
|
$this->warnings[IPV6DoubleColon::CODE] = new IPV6DoubleColon();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($colons === 0 || $colons === (strlen($IPv6) - 2)) {
|
||||||
|
// RFC 4291 allows :: at the start or end of an address
|
||||||
|
//with 7 other groups in addition
|
||||||
|
++$maxGroups;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($groupCount > $maxGroups) {
|
||||||
|
$this->warnings[IPV6MaxGroups::CODE] = new IPV6MaxGroups();
|
||||||
|
} elseif ($groupCount === $maxGroups) {
|
||||||
|
$this->warnings[IPV6Deprecated::CODE] = new IPV6Deprecated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convertIPv4ToIPv6(string $addressLiteralIPv4) : string
|
||||||
|
{
|
||||||
|
$matchesIP = [];
|
||||||
|
$IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteralIPv4, $matchesIP);
|
||||||
|
|
||||||
|
// Extract IPv4 part from the end of the address-literal (if there is one)
|
||||||
|
if ($IPv4Match > 0) {
|
||||||
|
$index = (int) strrpos($addressLiteralIPv4, $matchesIP[0]);
|
||||||
|
//There's a match but it is at the start
|
||||||
|
if ($index > 0) {
|
||||||
|
// Convert IPv4 part to IPv6 format for further testing
|
||||||
|
return substr($addressLiteralIPv4, 0, $index) . '0:0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $addressLiteralIPv4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $addressLiteral
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function checkIPV4Tag($addressLiteral) : bool
|
||||||
|
{
|
||||||
|
$matchesIP = [];
|
||||||
|
$IPv4Match = preg_match(self::IPV4_REGEX, $addressLiteral, $matchesIP);
|
||||||
|
|
||||||
|
// Extract IPv4 part from the end of the address-literal (if there is one)
|
||||||
|
|
||||||
|
if ($IPv4Match > 0) {
|
||||||
|
$index = strrpos($addressLiteral, $matchesIP[0]);
|
||||||
|
//There's a match but it is at the start
|
||||||
|
if ($index === 0) {
|
||||||
|
$this->warnings[AddressLiteral::CODE] = new AddressLiteral();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addObsoleteWarnings() : void
|
||||||
|
{
|
||||||
|
if(in_array($this->lexer->token['type'], self::OBSOLETE_WARNINGS)) {
|
||||||
|
$this->warnings[ObsoleteDTEXT::CODE] = new ObsoleteDTEXT();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addTagWarnings() : void
|
||||||
|
{
|
||||||
|
if ($this->lexer->isNextToken(EmailLexer::S_COLON)) {
|
||||||
|
$this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
|
||||||
|
}
|
||||||
|
if ($this->lexer->isNextToken(EmailLexer::S_IPV6TAG)) {
|
||||||
|
$lexer = clone $this->lexer;
|
||||||
|
$lexer->moveNext();
|
||||||
|
if ($lexer->isNextToken(EmailLexer::S_DOUBLECOLON)) {
|
||||||
|
$this->warnings[IPV6ColonStart::CODE] = new IPV6ColonStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,312 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Warning\TLD;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\DotAtEnd;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\DotAtStart;
|
||||||
|
use Egulias\EmailValidator\Warning\DeprecatedComment;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\CRLFAtTheEnd;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\LabelTooLong;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\NoDomainPart;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ConsecutiveAt;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\DomainTooLong;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\CharNotAllowed;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\DomainHyphened;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||||
|
use Egulias\EmailValidator\Parser\CommentStrategy\DomainComment;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingDomainLiteralClose;
|
||||||
|
use Egulias\EmailValidator\Parser\DomainLiteral as DomainLiteralParser;
|
||||||
|
|
||||||
|
class DomainPart extends PartParser
|
||||||
|
{
|
||||||
|
const DOMAIN_MAX_LENGTH = 253;
|
||||||
|
const LABEL_MAX_LENGTH = 63;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $domainPart = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $label = '';
|
||||||
|
|
||||||
|
public function parse() : Result
|
||||||
|
{
|
||||||
|
$this->lexer->clearRecorded();
|
||||||
|
$this->lexer->startRecording();
|
||||||
|
|
||||||
|
$this->lexer->moveNext();
|
||||||
|
|
||||||
|
$domainChecks = $this->performDomainStartChecks();
|
||||||
|
if ($domainChecks->isInvalid()) {
|
||||||
|
return $domainChecks;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_AT) {
|
||||||
|
return new InvalidEmail(new ConsecutiveAt(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->doParseDomainPart();
|
||||||
|
if ($result->isInvalid()) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
$end = $this->checkEndOfDomain();
|
||||||
|
if ($end->isInvalid()) {
|
||||||
|
return $end;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lexer->stopRecording();
|
||||||
|
$this->domainPart = $this->lexer->getAccumulatedValues();
|
||||||
|
|
||||||
|
$length = strlen($this->domainPart);
|
||||||
|
if ($length > self::DOMAIN_MAX_LENGTH) {
|
||||||
|
return new InvalidEmail(new DomainTooLong(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkEndOfDomain() : Result
|
||||||
|
{
|
||||||
|
$prev = $this->lexer->getPrevious();
|
||||||
|
if ($prev['type'] === EmailLexer::S_DOT) {
|
||||||
|
return new InvalidEmail(new DotAtEnd(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
if ($prev['type'] === EmailLexer::S_HYPHEN) {
|
||||||
|
return new InvalidEmail(new DomainHyphened('Hypen found at the end of the domain'), $prev['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_SP) {
|
||||||
|
return new InvalidEmail(new CRLFAtTheEnd(), $prev['value']);
|
||||||
|
}
|
||||||
|
return new ValidEmail();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function performDomainStartChecks() : Result
|
||||||
|
{
|
||||||
|
$invalidTokens = $this->checkInvalidTokensAfterAT();
|
||||||
|
if ($invalidTokens->isInvalid()) {
|
||||||
|
return $invalidTokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
$missingDomain = $this->checkEmptyDomain();
|
||||||
|
if ($missingDomain->isInvalid()) {
|
||||||
|
return $missingDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
|
||||||
|
$this->warnings[DeprecatedComment::CODE] = new DeprecatedComment();
|
||||||
|
}
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkEmptyDomain() : Result
|
||||||
|
{
|
||||||
|
$thereIsNoDomain = $this->lexer->token['type'] === EmailLexer::S_EMPTY ||
|
||||||
|
($this->lexer->token['type'] === EmailLexer::S_SP &&
|
||||||
|
!$this->lexer->isNextToken(EmailLexer::GENERIC));
|
||||||
|
|
||||||
|
if ($thereIsNoDomain) {
|
||||||
|
return new InvalidEmail(new NoDomainPart(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkInvalidTokensAfterAT() : Result
|
||||||
|
{
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_DOT) {
|
||||||
|
return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN) {
|
||||||
|
return new InvalidEmail(new DomainHyphened('After AT'), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseComments(): Result
|
||||||
|
{
|
||||||
|
$commentParser = new Comment($this->lexer, new DomainComment());
|
||||||
|
$result = $commentParser->parse();
|
||||||
|
$this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function doParseDomainPart() : Result
|
||||||
|
{
|
||||||
|
$tldMissing = true;
|
||||||
|
$hasComments = false;
|
||||||
|
$domain = '';
|
||||||
|
do {
|
||||||
|
$prev = $this->lexer->getPrevious();
|
||||||
|
|
||||||
|
$notAllowedChars = $this->checkNotAllowedChars($this->lexer->token);
|
||||||
|
if ($notAllowedChars->isInvalid()) {
|
||||||
|
return $notAllowedChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS ||
|
||||||
|
$this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) {
|
||||||
|
$hasComments = true;
|
||||||
|
$commentsResult = $this->parseComments();
|
||||||
|
|
||||||
|
//Invalid comment parsing
|
||||||
|
if($commentsResult->isInvalid()) {
|
||||||
|
return $commentsResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$dotsResult = $this->checkConsecutiveDots();
|
||||||
|
if ($dotsResult->isInvalid()) {
|
||||||
|
return $dotsResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET) {
|
||||||
|
$literalResult = $this->parseDomainLiteral();
|
||||||
|
|
||||||
|
$this->addTLDWarnings($tldMissing);
|
||||||
|
return $literalResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
$labelCheck = $this->checkLabelLength();
|
||||||
|
if ($labelCheck->isInvalid()) {
|
||||||
|
return $labelCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
$FwsResult = $this->parseFWS();
|
||||||
|
if($FwsResult->isInvalid()) {
|
||||||
|
return $FwsResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain .= $this->lexer->token['value'];
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::GENERIC)) {
|
||||||
|
$tldMissing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$exceptionsResult = $this->checkDomainPartExceptions($prev, $hasComments);
|
||||||
|
if ($exceptionsResult->isInvalid()) {
|
||||||
|
return $exceptionsResult;
|
||||||
|
}
|
||||||
|
$this->lexer->moveNext();
|
||||||
|
|
||||||
|
} while (null !== $this->lexer->token['type']);
|
||||||
|
|
||||||
|
$labelCheck = $this->checkLabelLength(true);
|
||||||
|
if ($labelCheck->isInvalid()) {
|
||||||
|
return $labelCheck;
|
||||||
|
}
|
||||||
|
$this->addTLDWarnings($tldMissing);
|
||||||
|
|
||||||
|
$this->domainPart = $domain;
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkNotAllowedChars(array $token) : Result
|
||||||
|
{
|
||||||
|
$notAllowed = [EmailLexer::S_BACKSLASH => true, EmailLexer::S_SLASH=> true];
|
||||||
|
if (isset($notAllowed[$token['type']])) {
|
||||||
|
return new InvalidEmail(new CharNotAllowed(), $token['value']);
|
||||||
|
}
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Result
|
||||||
|
*/
|
||||||
|
protected function parseDomainLiteral() : Result
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->lexer->find(EmailLexer::S_CLOSEBRACKET);
|
||||||
|
} catch (\RuntimeException $e) {
|
||||||
|
return new InvalidEmail(new ExpectingDomainLiteralClose(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$domainLiteralParser = new DomainLiteralParser($this->lexer);
|
||||||
|
$result = $domainLiteralParser->parse();
|
||||||
|
$this->warnings = array_merge($this->warnings, $domainLiteralParser->getWarnings());
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkDomainPartExceptions(array $prev, bool $hasComments) : Result
|
||||||
|
{
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_OPENBRACKET && $prev['type'] !== EmailLexer::S_AT) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT('OPENBRACKET not after AT'), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_HYPHEN && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
|
||||||
|
return new InvalidEmail(new DomainHyphened('Hypen found near DOT'), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH
|
||||||
|
&& $this->lexer->isNextToken(EmailLexer::GENERIC)) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT('Escaping following "ATOM"'), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->validateTokens($hasComments);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateTokens(bool $hasComments) : Result
|
||||||
|
{
|
||||||
|
$validDomainTokens = array(
|
||||||
|
EmailLexer::GENERIC => true,
|
||||||
|
EmailLexer::S_HYPHEN => true,
|
||||||
|
EmailLexer::S_DOT => true,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($hasComments) {
|
||||||
|
$validDomainTokens[EmailLexer::S_OPENPARENTHESIS] = true;
|
||||||
|
$validDomainTokens[EmailLexer::S_CLOSEPARENTHESIS] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($validDomainTokens[$this->lexer->token['type']])) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function checkLabelLength(bool $isEndOfDomain = false) : Result
|
||||||
|
{
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_DOT || $isEndOfDomain) {
|
||||||
|
if ($this->isLabelTooLong($this->label)) {
|
||||||
|
return new InvalidEmail(new LabelTooLong(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
$this->label = '';
|
||||||
|
}
|
||||||
|
$this->label .= $this->lexer->token['value'];
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function isLabelTooLong(string $label) : bool
|
||||||
|
{
|
||||||
|
if (preg_match('/[^\x00-\x7F]/', $label)) {
|
||||||
|
idn_to_ascii($label, IDNA_DEFAULT, INTL_IDNA_VARIANT_UTS46, $idnaInfo);
|
||||||
|
return (bool) ($idnaInfo['errors'] & IDNA_ERROR_LABEL_TOO_LONG);
|
||||||
|
}
|
||||||
|
return strlen($label) > self::LABEL_MAX_LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function addTLDWarnings(bool $isTLDMissing) : void
|
||||||
|
{
|
||||||
|
if ($isTLDMissing) {
|
||||||
|
$this->warnings[TLD::CODE] = new TLD();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function domainPart() : string
|
||||||
|
{
|
||||||
|
return $this->domainPart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?php
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Parser\Parser;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Warning\CFWSWithFWS;
|
||||||
|
use Egulias\EmailValidator\Warning\QuotedString;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\UnclosedQuotedString;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
|
||||||
|
class DoubleQuote extends PartParser
|
||||||
|
{
|
||||||
|
public function parse() : Result
|
||||||
|
{
|
||||||
|
|
||||||
|
$validQuotedString = $this->checkDQUOTE();
|
||||||
|
if($validQuotedString->isInvalid()) return $validQuotedString;
|
||||||
|
|
||||||
|
$special = [
|
||||||
|
EmailLexer::S_CR => true,
|
||||||
|
EmailLexer::S_HTAB => true,
|
||||||
|
EmailLexer::S_LF => true
|
||||||
|
];
|
||||||
|
|
||||||
|
$invalid = [
|
||||||
|
EmailLexer::C_NUL => true,
|
||||||
|
EmailLexer::S_HTAB => true,
|
||||||
|
EmailLexer::S_CR => true,
|
||||||
|
EmailLexer::S_LF => true
|
||||||
|
];
|
||||||
|
|
||||||
|
$setSpecialsWarning = true;
|
||||||
|
|
||||||
|
$this->lexer->moveNext();
|
||||||
|
|
||||||
|
while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && null !== $this->lexer->token['type']) {
|
||||||
|
if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) {
|
||||||
|
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
|
||||||
|
$setSpecialsWarning = false;
|
||||||
|
}
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) {
|
||||||
|
$this->lexer->moveNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lexer->moveNext();
|
||||||
|
|
||||||
|
if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT("Expecting ATEXT between DQUOTE"), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$prev = $this->lexer->getPrevious();
|
||||||
|
|
||||||
|
if ($prev['type'] === EmailLexer::S_BACKSLASH) {
|
||||||
|
$validQuotedString = $this->checkDQUOTE();
|
||||||
|
if($validQuotedString->isInvalid()) return $validQuotedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT("Expecting ATEXT between DQUOTE"), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkDQUOTE() : Result
|
||||||
|
{
|
||||||
|
$previous = $this->lexer->getPrevious();
|
||||||
|
|
||||||
|
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] === EmailLexer::GENERIC) {
|
||||||
|
$description = 'https://tools.ietf.org/html/rfc5322#section-3.2.4 - quoted string should be a unit';
|
||||||
|
return new InvalidEmail(new ExpectingATEXT($description), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$this->lexer->find(EmailLexer::S_DQUOTE);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return new InvalidEmail(new UnclosedQuotedString(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
$this->warnings[QuotedString::CODE] = new QuotedString($previous['value'], $this->lexer->token['value']);
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Warning\CFWSNearAt;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Warning\CFWSWithFWS;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\CRNoLF;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\AtextAfterCFWS;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\CRLFAtTheEnd;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\CRLFX2;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingCTEXT;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
|
||||||
|
class FoldingWhiteSpace extends PartParser
|
||||||
|
{
|
||||||
|
const FWS_TYPES = [
|
||||||
|
EmailLexer::S_SP,
|
||||||
|
EmailLexer::S_HTAB,
|
||||||
|
EmailLexer::S_CR,
|
||||||
|
EmailLexer::S_LF,
|
||||||
|
EmailLexer::CRLF
|
||||||
|
];
|
||||||
|
|
||||||
|
public function parse() : Result
|
||||||
|
{
|
||||||
|
if (!$this->isFWS()) {
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
$previous = $this->lexer->getPrevious();
|
||||||
|
|
||||||
|
$resultCRLF = $this->checkCRLFInFWS();
|
||||||
|
if ($resultCRLF->isInvalid()) {
|
||||||
|
return $resultCRLF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_CR) {
|
||||||
|
return new InvalidEmail(new CRNoLF(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->isNextToken(EmailLexer::GENERIC) && $previous['type'] !== EmailLexer::S_AT) {
|
||||||
|
return new InvalidEmail(new AtextAfterCFWS(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_LF || $this->lexer->token['type'] === EmailLexer::C_NUL) {
|
||||||
|
return new InvalidEmail(new ExpectingCTEXT(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->isNextToken(EmailLexer::S_AT) || $previous['type'] === EmailLexer::S_AT) {
|
||||||
|
$this->warnings[CFWSNearAt::CODE] = new CFWSNearAt();
|
||||||
|
} else {
|
||||||
|
$this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkCRLFInFWS() : Result
|
||||||
|
{
|
||||||
|
if ($this->lexer->token['type'] !== EmailLexer::CRLF) {
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
|
||||||
|
return new InvalidEmail(new CRLFX2(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this has no coverage. Condition is repeated from above one
|
||||||
|
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB))) {
|
||||||
|
return new InvalidEmail(new CRLFAtTheEnd(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function isFWS() : bool
|
||||||
|
{
|
||||||
|
if ($this->escaped()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_array($this->lexer->token['type'], self::FWS_TYPES);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Parser\LocalPart;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\CommentsInIDRight;
|
||||||
|
|
||||||
|
class IDLeftPart extends LocalPart
|
||||||
|
{
|
||||||
|
protected function parseComments(): Result
|
||||||
|
{
|
||||||
|
return new InvalidEmail(new CommentsInIDRight(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||||
|
|
||||||
|
class IDRightPart extends DomainPart
|
||||||
|
{
|
||||||
|
protected function validateTokens(bool $hasComments) : Result
|
||||||
|
{
|
||||||
|
$invalidDomainTokens = [
|
||||||
|
EmailLexer::S_DQUOTE => true,
|
||||||
|
EmailLexer::S_SQUOTE => true,
|
||||||
|
EmailLexer::S_BACKTICK => true,
|
||||||
|
EmailLexer::S_SEMICOLON => true,
|
||||||
|
EmailLexer::S_GREATERTHAN => true,
|
||||||
|
EmailLexer::S_LOWERTHAN => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (isset($invalidDomainTokens[$this->lexer->token['type']])) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT('Invalid token in domain: ' . $this->lexer->token['value']), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Warning\LocalTooLong;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\DotAtEnd;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\DotAtStart;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ConsecutiveDot;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExpectingATEXT;
|
||||||
|
use Egulias\EmailValidator\Parser\CommentStrategy\LocalComment;
|
||||||
|
|
||||||
|
class LocalPart extends PartParser
|
||||||
|
{
|
||||||
|
const INVALID_TOKENS = [
|
||||||
|
EmailLexer::S_COMMA => EmailLexer::S_COMMA,
|
||||||
|
EmailLexer::S_CLOSEBRACKET => EmailLexer::S_CLOSEBRACKET,
|
||||||
|
EmailLexer::S_OPENBRACKET => EmailLexer::S_OPENBRACKET,
|
||||||
|
EmailLexer::S_GREATERTHAN => EmailLexer::S_GREATERTHAN,
|
||||||
|
EmailLexer::S_LOWERTHAN => EmailLexer::S_LOWERTHAN,
|
||||||
|
EmailLexer::S_COLON => EmailLexer::S_COLON,
|
||||||
|
EmailLexer::S_SEMICOLON => EmailLexer::S_SEMICOLON,
|
||||||
|
EmailLexer::INVALID => EmailLexer::INVALID
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $localPart = '';
|
||||||
|
|
||||||
|
|
||||||
|
public function parse() : Result
|
||||||
|
{
|
||||||
|
$this->lexer->startRecording();
|
||||||
|
|
||||||
|
while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) {
|
||||||
|
if ($this->hasDotAtStart()) {
|
||||||
|
return new InvalidEmail(new DotAtStart(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_DQUOTE) {
|
||||||
|
$dquoteParsingResult = $this->parseDoubleQuote();
|
||||||
|
|
||||||
|
//Invalid double quote parsing
|
||||||
|
if($dquoteParsingResult->isInvalid()) {
|
||||||
|
return $dquoteParsingResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS ||
|
||||||
|
$this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS ) {
|
||||||
|
$commentsResult = $this->parseComments();
|
||||||
|
|
||||||
|
//Invalid comment parsing
|
||||||
|
if($commentsResult->isInvalid()) {
|
||||||
|
return $commentsResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
|
||||||
|
return new InvalidEmail(new ConsecutiveDot(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
|
||||||
|
$this->lexer->isNextToken(EmailLexer::S_AT)
|
||||||
|
) {
|
||||||
|
return new InvalidEmail(new DotAtEnd(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultEscaping = $this->validateEscaping();
|
||||||
|
if ($resultEscaping->isInvalid()) {
|
||||||
|
return $resultEscaping;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultToken = $this->validateTokens(false);
|
||||||
|
if ($resultToken->isInvalid()) {
|
||||||
|
return $resultToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
$resultFWS = $this->parseLocalFWS();
|
||||||
|
if($resultFWS->isInvalid()) {
|
||||||
|
return $resultFWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lexer->moveNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->lexer->stopRecording();
|
||||||
|
$this->localPart = rtrim($this->lexer->getAccumulatedValues(), '@');
|
||||||
|
if (strlen($this->localPart) > LocalTooLong::LOCAL_PART_LENGTH) {
|
||||||
|
$this->warnings[LocalTooLong::CODE] = new LocalTooLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function validateTokens(bool $hasComments) : Result
|
||||||
|
{
|
||||||
|
if (isset(self::INVALID_TOKENS[$this->lexer->token['type']])) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT('Invalid token found'), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function localPart() : string
|
||||||
|
{
|
||||||
|
return $this->localPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseLocalFWS() : Result
|
||||||
|
{
|
||||||
|
$foldingWS = new FoldingWhiteSpace($this->lexer);
|
||||||
|
$resultFWS = $foldingWS->parse();
|
||||||
|
if ($resultFWS->isValid()) {
|
||||||
|
$this->warnings = array_merge($this->warnings, $foldingWS->getWarnings());
|
||||||
|
}
|
||||||
|
return $resultFWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasDotAtStart() : bool
|
||||||
|
{
|
||||||
|
return $this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseDoubleQuote() : Result
|
||||||
|
{
|
||||||
|
$dquoteParser = new DoubleQuote($this->lexer);
|
||||||
|
$parseAgain = $dquoteParser->parse();
|
||||||
|
$this->warnings = array_merge($this->warnings, $dquoteParser->getWarnings());
|
||||||
|
|
||||||
|
return $parseAgain;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseComments(): Result
|
||||||
|
{
|
||||||
|
$commentParser = new Comment($this->lexer, new LocalComment());
|
||||||
|
$result = $commentParser->parse();
|
||||||
|
$this->warnings = array_merge($this->warnings, $commentParser->getWarnings());
|
||||||
|
if($result->isInvalid()) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateEscaping() : Result
|
||||||
|
{
|
||||||
|
//Backslash found
|
||||||
|
if ($this->lexer->token['type'] !== EmailLexer::S_BACKSLASH) {
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->lexer->isNextToken(EmailLexer::GENERIC)) {
|
||||||
|
return new InvalidEmail(new ExpectingATEXT('Found ATOM after escaping'), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->lexer->isNextTokenAny(array(EmailLexer::S_SP, EmailLexer::S_HTAB, EmailLexer::C_DEL))) {
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Parser;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ConsecutiveDot;
|
||||||
|
use Egulias\EmailValidator\Result\Result;
|
||||||
|
use Egulias\EmailValidator\Result\ValidEmail;
|
||||||
|
|
||||||
|
abstract class PartParser
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $warnings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var EmailLexer
|
||||||
|
*/
|
||||||
|
protected $lexer;
|
||||||
|
|
||||||
|
public function __construct(EmailLexer $lexer)
|
||||||
|
{
|
||||||
|
$this->lexer = $lexer;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function parse() : Result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Egulias\EmailValidator\Warning\Warning[]
|
||||||
|
*/
|
||||||
|
public function getWarnings()
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function parseFWS() : Result
|
||||||
|
{
|
||||||
|
$foldingWS = new FoldingWhiteSpace($this->lexer);
|
||||||
|
$resultFWS = $foldingWS->parse();
|
||||||
|
$this->warnings = array_merge($this->warnings, $foldingWS->getWarnings());
|
||||||
|
return $resultFWS;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function checkConsecutiveDots() : Result
|
||||||
|
{
|
||||||
|
if ($this->lexer->token['type'] === EmailLexer::S_DOT && $this->lexer->isNextToken(EmailLexer::S_DOT)) {
|
||||||
|
return new InvalidEmail(new ConsecutiveDot(), $this->lexer->token['value']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ValidEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function escaped() : bool
|
||||||
|
{
|
||||||
|
$previous = $this->lexer->getPrevious();
|
||||||
|
|
||||||
|
return $previous && $previous['type'] === EmailLexer::S_BACKSLASH
|
||||||
|
&&
|
||||||
|
$this->lexer->token['type'] !== EmailLexer::GENERIC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\Result\Reason\Reason;
|
||||||
|
|
||||||
|
class InvalidEmail implements Result
|
||||||
|
{
|
||||||
|
private $token;
|
||||||
|
/**
|
||||||
|
* @var Reason
|
||||||
|
*/
|
||||||
|
protected $reason;
|
||||||
|
|
||||||
|
public function __construct(Reason $reason, string $token)
|
||||||
|
{
|
||||||
|
$this->token = $token;
|
||||||
|
$this->reason = $reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValid(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isInvalid(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description(): string
|
||||||
|
{
|
||||||
|
return $this->reason->description() . " in char " . $this->token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function code(): int
|
||||||
|
{
|
||||||
|
return $this->reason->code();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reason() : Reason
|
||||||
|
{
|
||||||
|
return $this->reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\Result\Reason\EmptyReason;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\Reason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-suppress PropertyNotSetInConstructor
|
||||||
|
*/
|
||||||
|
class MultipleErrors extends InvalidEmail
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Reason[]
|
||||||
|
*/
|
||||||
|
private $reasons = [];
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addReason(Reason $reason) : void
|
||||||
|
{
|
||||||
|
$this->reasons[$reason->code()] = $reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Reason[]
|
||||||
|
*/
|
||||||
|
public function getReasons() : array
|
||||||
|
{
|
||||||
|
return $this->reasons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function reason() : Reason
|
||||||
|
{
|
||||||
|
return 0 !== count($this->reasons)
|
||||||
|
? current($this->reasons)
|
||||||
|
: new EmptyReason();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
$description = '';
|
||||||
|
foreach($this->reasons as $reason) {
|
||||||
|
$description .= $reason->description() . PHP_EOL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class AtextAfterCFWS implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 133;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'ATEXT found after CFWS';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class CRLFAtTheEnd implements Reason
|
||||||
|
{
|
||||||
|
const CODE = 149;
|
||||||
|
const REASON = "CRLF at the end";
|
||||||
|
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 149;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'CRLF at the end';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class CRLFX2 implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 148;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'CR LF tokens found twice';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class CRNoLF implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 150;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Missing LF after CR';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class CharNotAllowed implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return "Character not allowed";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class CommaInDomain implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return "Comma ',' is not allowed in domain part";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class CommentsInIDRight implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Comments are not allowed in IDRight for message-id';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class ConsecutiveAt implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return '@ found after another @';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class ConsecutiveDot implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 132;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Concecutive DOT found';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
abstract class DetailedReason implements Reason
|
||||||
|
{
|
||||||
|
protected $detailedDescription;
|
||||||
|
|
||||||
|
public function __construct(string $details)
|
||||||
|
{
|
||||||
|
$this->detailedDescription = $details;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class DomainAcceptsNoMail implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 154;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Domain accepts no mail (Null MX, RFC7505)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class DomainHyphened extends DetailedReason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 144;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'S_HYPHEN found in domain';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class DomainTooLong implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 244;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Domain is longer than 253 characters';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class DotAtEnd implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 142;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Dot at the end';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class DotAtStart implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 141;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return "Starts with a DOT";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class EmptyReason implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Empty reason';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class ExceptionFound implements Reason
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Exception
|
||||||
|
*/
|
||||||
|
private $exception;
|
||||||
|
|
||||||
|
public function __construct(\Exception $exception)
|
||||||
|
{
|
||||||
|
$this->exception = $exception;
|
||||||
|
|
||||||
|
}
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 999;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return $this->exception->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class ExpectingATEXT extends DetailedReason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 137;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return "Expecting ATEXT (Printable US-ASCII). Extended: " . $this->detailedDescription;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class ExpectingCTEXT implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 139;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Expecting CTEXT';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class ExpectingDTEXT implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 129;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Expecting DTEXT';
|
||||||
|
}
|
||||||
|
}
|
||||||
16
vendor/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php
vendored
Normal file
16
vendor/egulias/email-validator/src/Result/Reason/ExpectingDomainLiteralClose.php
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class ExpectingDomainLiteralClose implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 137;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return "Closing bracket ']' for domain literal not found";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class LabelTooLong implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 245;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Domain "label" is longer than 63 characters';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class LocalOrReservedDomain implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 153;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Local, mDNS or reserved domain (RFC2606, RFC6762)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class NoDNSRecord implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'No MX or A DSN record was found for this email';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class NoDomainPart implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 131;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'No domain part found';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class NoLocalPart implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 130;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return "No local part";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class RFCWarnings implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 997;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Warnings found after validating';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
interface Reason
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Code for user land to act upon;
|
||||||
|
*/
|
||||||
|
public function code() : int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short description of the result, human readable.
|
||||||
|
*/
|
||||||
|
public function description() : string;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class SpoofEmail implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 298;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'The email contains mixed UTF8 chars that makes it suspicious';
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class UnOpenedComment implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 152;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description(): string
|
||||||
|
{
|
||||||
|
return 'Missing opening comment parentheses - https://tools.ietf.org/html/rfc5322#section-3.2.2';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used on SERVFAIL, TIMEOUT or other runtime and network errors
|
||||||
|
*/
|
||||||
|
class UnableToGetDNSRecord extends NoDNSRecord
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Unable to get DNS records for the host';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class UnclosedComment implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 146;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description(): string
|
||||||
|
{
|
||||||
|
return 'No closing comment token found';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class UnclosedQuotedString implements Reason
|
||||||
|
{
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 145;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return "Unclosed quoted string";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result\Reason;
|
||||||
|
|
||||||
|
class UnusualElements implements Reason
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string $element
|
||||||
|
*/
|
||||||
|
private $element = '';
|
||||||
|
|
||||||
|
public function __construct(string $element)
|
||||||
|
{
|
||||||
|
$this->element = $element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function code() : int
|
||||||
|
{
|
||||||
|
return 201;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description() : string
|
||||||
|
{
|
||||||
|
return 'Unusual element found, wourld render invalid in majority of cases. Element found: ' . $this->element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result;
|
||||||
|
|
||||||
|
interface Result
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Is validation result valid?
|
||||||
|
*/
|
||||||
|
public function isValid() : bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is validation result invalid?
|
||||||
|
* Usually the inverse of isValid()
|
||||||
|
*/
|
||||||
|
public function isInvalid() : bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Short description of the result, human readable.
|
||||||
|
*/
|
||||||
|
public function description() : string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Code for user land to act upon.
|
||||||
|
*/
|
||||||
|
public function code() : int;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
namespace Egulias\EmailValidator\Result;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\SpoofEmail as ReasonSpoofEmail;
|
||||||
|
|
||||||
|
class SpoofEmail extends InvalidEmail
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->reason = new ReasonSpoofEmail();
|
||||||
|
parent::__construct($this->reason, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Result;
|
||||||
|
|
||||||
|
class ValidEmail implements Result
|
||||||
|
{
|
||||||
|
public function isValid(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isInvalid(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description(): string
|
||||||
|
{
|
||||||
|
return "Valid email";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function code(): int
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,192 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\Validation\DNSGetRecordWrapper;
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\DomainAcceptsNoMail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\LocalOrReservedDomain;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\NoDNSRecord as ReasonNoDNSRecord;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\UnableToGetDNSRecord;
|
||||||
|
use Egulias\EmailValidator\Warning\NoDNSMXRecord;
|
||||||
|
|
||||||
|
class DNSCheckValidation implements EmailValidation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected const DNS_RECORD_TYPES_TO_CHECK = DNS_MX + DNS_A + DNS_AAAA;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reserved Top Level DNS Names (https://tools.ietf.org/html/rfc2606#section-2),
|
||||||
|
* mDNS and private DNS Namespaces (https://tools.ietf.org/html/rfc6762#appendix-G)
|
||||||
|
*/
|
||||||
|
const RESERVED_DNS_TOP_LEVEL_NAMES = [
|
||||||
|
// Reserved Top Level DNS Names
|
||||||
|
'test',
|
||||||
|
'example',
|
||||||
|
'invalid',
|
||||||
|
'localhost',
|
||||||
|
|
||||||
|
// mDNS
|
||||||
|
'local',
|
||||||
|
|
||||||
|
// Private DNS Namespaces
|
||||||
|
'intranet',
|
||||||
|
'internal',
|
||||||
|
'private',
|
||||||
|
'corp',
|
||||||
|
'home',
|
||||||
|
'lan',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $warnings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var InvalidEmail|null
|
||||||
|
*/
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $mxRecords = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var DNSGetRecordWrapper
|
||||||
|
*/
|
||||||
|
private $dnsGetRecord;
|
||||||
|
|
||||||
|
public function __construct(DNSGetRecordWrapper $dnsGetRecord = null)
|
||||||
|
{
|
||||||
|
if (!function_exists('idn_to_ascii')) {
|
||||||
|
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dnsGetRecord == null) {
|
||||||
|
$dnsGetRecord = new DNSGetRecordWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dnsGetRecord = $dnsGetRecord;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isValid(string $email, EmailLexer $emailLexer) : bool
|
||||||
|
{
|
||||||
|
// use the input to check DNS if we cannot extract something similar to a domain
|
||||||
|
$host = $email;
|
||||||
|
|
||||||
|
// Arguable pattern to extract the domain. Not aiming to validate the domain nor the email
|
||||||
|
if (false !== $lastAtPos = strrpos($email, '@')) {
|
||||||
|
$host = substr($email, $lastAtPos + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the domain parts
|
||||||
|
$hostParts = explode('.', $host);
|
||||||
|
|
||||||
|
$isLocalDomain = count($hostParts) <= 1;
|
||||||
|
$isReservedTopLevel = in_array($hostParts[(count($hostParts) - 1)], self::RESERVED_DNS_TOP_LEVEL_NAMES, true);
|
||||||
|
|
||||||
|
// Exclude reserved top level DNS names
|
||||||
|
if ($isLocalDomain || $isReservedTopLevel) {
|
||||||
|
$this->error = new InvalidEmail(new LocalOrReservedDomain(), $host);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->checkDns($host);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getError() : ?InvalidEmail
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWarnings() : array
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $host
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function checkDns($host)
|
||||||
|
{
|
||||||
|
$variant = INTL_IDNA_VARIANT_UTS46;
|
||||||
|
|
||||||
|
$host = rtrim(idn_to_ascii($host, IDNA_DEFAULT, $variant), '.') . '.';
|
||||||
|
|
||||||
|
return $this->validateDnsRecords($host);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the DNS records for given host.
|
||||||
|
*
|
||||||
|
* @param string $host A set of DNS records in the format returned by dns_get_record.
|
||||||
|
*
|
||||||
|
* @return bool True on success.
|
||||||
|
*/
|
||||||
|
private function validateDnsRecords($host) : bool
|
||||||
|
{
|
||||||
|
$dnsRecordsResult = $this->dnsGetRecord->getRecords($host, static::DNS_RECORD_TYPES_TO_CHECK);
|
||||||
|
|
||||||
|
if ($dnsRecordsResult->withError()) {
|
||||||
|
$this->error = new InvalidEmail(new UnableToGetDNSRecord(), '');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dnsRecords = $dnsRecordsResult->getRecords();
|
||||||
|
|
||||||
|
// No MX, A or AAAA DNS records
|
||||||
|
if ($dnsRecords === []) {
|
||||||
|
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each DNS record
|
||||||
|
foreach ($dnsRecords as $dnsRecord) {
|
||||||
|
if (!$this->validateMXRecord($dnsRecord)) {
|
||||||
|
// No MX records (fallback to A or AAAA records)
|
||||||
|
if (empty($this->mxRecords)) {
|
||||||
|
$this->warnings[NoDNSMXRecord::CODE] = new NoDNSMXRecord();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate an MX record
|
||||||
|
*
|
||||||
|
* @param array $dnsRecord Given DNS record.
|
||||||
|
*
|
||||||
|
* @return bool True if valid.
|
||||||
|
*/
|
||||||
|
private function validateMxRecord($dnsRecord) : bool
|
||||||
|
{
|
||||||
|
if (!isset($dnsRecord['type'])) {
|
||||||
|
$this->error = new InvalidEmail(new ReasonNoDNSRecord(), '');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($dnsRecord['type'] !== 'MX') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Null MX" record indicates the domain accepts no mail (https://tools.ietf.org/html/rfc7505)
|
||||||
|
if (empty($dnsRecord['target']) || $dnsRecord['target'] === '.') {
|
||||||
|
$this->error = new InvalidEmail(new DomainAcceptsNoMail(), "");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->mxRecords[] = $dnsRecord;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
namespace Egulias\EmailValidator\Validation;
|
||||||
|
|
||||||
|
class DNSGetRecordWrapper
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param string $host
|
||||||
|
* @param int $type
|
||||||
|
*/
|
||||||
|
public function getRecords(string $host, int $type) : DNSRecords
|
||||||
|
{
|
||||||
|
// A workaround to fix https://bugs.php.net/bug.php?id=73149
|
||||||
|
/** @psalm-suppress InvalidArgument */
|
||||||
|
set_error_handler(
|
||||||
|
static function (int $errorLevel, string $errorMessage): ?bool {
|
||||||
|
throw new \RuntimeException("Unable to get DNS record for the host: $errorMessage");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
// Get all MX, A and AAAA DNS records for host
|
||||||
|
return new DNSRecords(dns_get_record($host, $type));
|
||||||
|
} catch (\RuntimeException $exception) {
|
||||||
|
return new DNSRecords([], true);
|
||||||
|
} finally {
|
||||||
|
restore_error_handler();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation;
|
||||||
|
|
||||||
|
class DNSRecords
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array $records
|
||||||
|
*/
|
||||||
|
private $records = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool $error
|
||||||
|
*/
|
||||||
|
private $error = false;
|
||||||
|
|
||||||
|
public function __construct(array $records, bool $error = false)
|
||||||
|
{
|
||||||
|
$this->records = $records;
|
||||||
|
$this->error = $error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getRecords() : array
|
||||||
|
{
|
||||||
|
return $this->records;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withError() : bool
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Warning\Warning;
|
||||||
|
|
||||||
|
interface EmailValidation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns true if the given email is valid.
|
||||||
|
*
|
||||||
|
* @param string $email The email you want to validate.
|
||||||
|
* @param EmailLexer $emailLexer The email lexer.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isValid(string $email, EmailLexer $emailLexer) : bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the validation error.
|
||||||
|
*
|
||||||
|
* @return InvalidEmail|null
|
||||||
|
*/
|
||||||
|
public function getError() : ?InvalidEmail;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the validation warnings.
|
||||||
|
*
|
||||||
|
* @return Warning[]
|
||||||
|
*/
|
||||||
|
public function getWarnings() : array;
|
||||||
|
}
|
||||||
16
vendor/egulias/email-validator/src/Validation/Exception/EmptyValidationList.php
vendored
Normal file
16
vendor/egulias/email-validator/src/Validation/Exception/EmptyValidationList.php
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class EmptyValidationList extends \InvalidArgumentException
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param int $code
|
||||||
|
*/
|
||||||
|
public function __construct($code = 0, Exception $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct("Empty validation list is not allowed", $code, $previous);
|
||||||
|
}
|
||||||
|
}
|
||||||
52
vendor/egulias/email-validator/src/Validation/Extra/SpoofCheckValidation.php
vendored
Normal file
52
vendor/egulias/email-validator/src/Validation/Extra/SpoofCheckValidation.php
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation\Extra;
|
||||||
|
|
||||||
|
use \Spoofchecker;
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\SpoofEmail;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Validation\EmailValidation;
|
||||||
|
|
||||||
|
class SpoofCheckValidation implements EmailValidation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var InvalidEmail|null
|
||||||
|
*/
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
if (!extension_loaded('intl')) {
|
||||||
|
throw new \LogicException(sprintf('The %s class requires the Intl extension.', __CLASS__));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-suppress InvalidArgument
|
||||||
|
*/
|
||||||
|
public function isValid(string $email, EmailLexer $emailLexer) : bool
|
||||||
|
{
|
||||||
|
$checker = new Spoofchecker();
|
||||||
|
$checker->setChecks(Spoofchecker::SINGLE_SCRIPT);
|
||||||
|
|
||||||
|
if ($checker->isSuspicious($email)) {
|
||||||
|
$this->error = new SpoofEmail();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->error === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return InvalidEmail
|
||||||
|
*/
|
||||||
|
public function getError() : ?InvalidEmail
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWarnings() : array
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\MessageIDParser;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExceptionFound;
|
||||||
|
|
||||||
|
class MessageIDValidation implements EmailValidation
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $warnings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ?InvalidEmail
|
||||||
|
*/
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
public function isValid(string $email, EmailLexer $emailLexer): bool
|
||||||
|
{
|
||||||
|
$parser = new MessageIDParser($emailLexer);
|
||||||
|
try {
|
||||||
|
$result = $parser->parse($email);
|
||||||
|
$this->warnings = $parser->getWarnings();
|
||||||
|
if ($result->isInvalid()) {
|
||||||
|
/** @psalm-suppress PropertyTypeCoercion */
|
||||||
|
$this->error = $result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (\Exception $invalid) {
|
||||||
|
$this->error = new InvalidEmail(new ExceptionFound($invalid), '');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWarnings(): array
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getError(): ?InvalidEmail
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
}
|
||||||
117
vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php
vendored
Normal file
117
vendor/egulias/email-validator/src/Validation/MultipleValidationWithAnd.php
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Validation\Exception\EmptyValidationList;
|
||||||
|
use Egulias\EmailValidator\Result\MultipleErrors;
|
||||||
|
|
||||||
|
class MultipleValidationWithAnd implements EmailValidation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* If one of validations fails, the remaining validations will be skipped.
|
||||||
|
* This means MultipleErrors will only contain a single error, the first found.
|
||||||
|
*/
|
||||||
|
const STOP_ON_ERROR = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All of validations will be invoked even if one of them got failure.
|
||||||
|
* So MultipleErrors will contain all causes.
|
||||||
|
*/
|
||||||
|
const ALLOW_ALL_ERRORS = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var EmailValidation[]
|
||||||
|
*/
|
||||||
|
private $validations = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $warnings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MultipleErrors|null
|
||||||
|
*/
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $mode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param EmailValidation[] $validations The validations.
|
||||||
|
* @param int $mode The validation mode (one of the constants).
|
||||||
|
*/
|
||||||
|
public function __construct(array $validations, $mode = self::ALLOW_ALL_ERRORS)
|
||||||
|
{
|
||||||
|
if (count($validations) == 0) {
|
||||||
|
throw new EmptyValidationList();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->validations = $validations;
|
||||||
|
$this->mode = $mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isValid(string $email, EmailLexer $emailLexer) : bool
|
||||||
|
{
|
||||||
|
$result = true;
|
||||||
|
foreach ($this->validations as $validation) {
|
||||||
|
$emailLexer->reset();
|
||||||
|
$validationResult = $validation->isValid($email, $emailLexer);
|
||||||
|
$result = $result && $validationResult;
|
||||||
|
$this->warnings = array_merge($this->warnings, $validation->getWarnings());
|
||||||
|
if (!$validationResult) {
|
||||||
|
$this->processError($validation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->shouldStop($result)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function initErrorStorage() : void
|
||||||
|
{
|
||||||
|
if (null === $this->error) {
|
||||||
|
$this->error = new MultipleErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function processError(EmailValidation $validation) : void
|
||||||
|
{
|
||||||
|
if (null !== $validation->getError()) {
|
||||||
|
$this->initErrorStorage();
|
||||||
|
/** @psalm-suppress PossiblyNullReference */
|
||||||
|
$this->error->addReason($validation->getError()->reason());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function shouldStop(bool $result) : bool
|
||||||
|
{
|
||||||
|
return !$result && $this->mode === self::STOP_ON_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the validation errors.
|
||||||
|
*/
|
||||||
|
public function getError() : ?InvalidEmail
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getWarnings() : array
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\RFCWarnings;
|
||||||
|
|
||||||
|
class NoRFCWarningsValidation extends RFCValidation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var InvalidEmail|null
|
||||||
|
*/
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function isValid(string $email, EmailLexer $emailLexer) : bool
|
||||||
|
{
|
||||||
|
if (!parent::isValid($email, $emailLexer)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($this->getWarnings())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->error = new InvalidEmail(new RFCWarnings(), '');
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getError() : ?InvalidEmail
|
||||||
|
{
|
||||||
|
return $this->error ?: parent::getError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Validation;
|
||||||
|
|
||||||
|
use Egulias\EmailValidator\EmailLexer;
|
||||||
|
use Egulias\EmailValidator\EmailParser;
|
||||||
|
use Egulias\EmailValidator\Result\InvalidEmail;
|
||||||
|
use Egulias\EmailValidator\Result\Reason\ExceptionFound;
|
||||||
|
|
||||||
|
class RFCValidation implements EmailValidation
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var EmailParser|null
|
||||||
|
*/
|
||||||
|
private $parser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
private $warnings = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ?InvalidEmail
|
||||||
|
*/
|
||||||
|
private $error;
|
||||||
|
|
||||||
|
public function isValid(string $email, EmailLexer $emailLexer) : bool
|
||||||
|
{
|
||||||
|
$this->parser = new EmailParser($emailLexer);
|
||||||
|
try {
|
||||||
|
$result = $this->parser->parse($email);
|
||||||
|
$this->warnings = $this->parser->getWarnings();
|
||||||
|
if ($result->isInvalid()) {
|
||||||
|
/** @psalm-suppress PropertyTypeCoercion */
|
||||||
|
$this->error = $result;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (\Exception $invalid) {
|
||||||
|
$this->error = new InvalidEmail(new ExceptionFound($invalid), '');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getError() : ?InvalidEmail
|
||||||
|
{
|
||||||
|
return $this->error;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getWarnings() : array
|
||||||
|
{
|
||||||
|
return $this->warnings;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Warning;
|
||||||
|
|
||||||
|
class AddressLiteral extends Warning
|
||||||
|
{
|
||||||
|
const CODE = 12;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->message = 'Address literal in domain part';
|
||||||
|
$this->rfcNumber = 5321;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Warning;
|
||||||
|
|
||||||
|
class CFWSNearAt extends Warning
|
||||||
|
{
|
||||||
|
const CODE = 49;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->message = "Deprecated folding white space near @";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Warning;
|
||||||
|
|
||||||
|
class CFWSWithFWS extends Warning
|
||||||
|
{
|
||||||
|
const CODE = 18;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->message = 'Folding whites space followed by folding white space';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Warning;
|
||||||
|
|
||||||
|
class Comment extends Warning
|
||||||
|
{
|
||||||
|
const CODE = 17;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->message = "Comments found in this email";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Egulias\EmailValidator\Warning;
|
||||||
|
|
||||||
|
class DeprecatedComment extends Warning
|
||||||
|
{
|
||||||
|
const CODE = 37;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->message = 'Deprecated comments';
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue